7
头图

foreword

Hello everyone, I'm Lin Sanxin, uses the most simple and easy-to-understand words to describe the most difficult knowledge points. is my motto. is the premise of .

background

When everyone is developing Vue, most of them use the ready-made Vue scaffolding Vue-cli for development, but after using it for so long, don't you want to build your own Vue-cli ?

Today I will take you to build a basic Vue-cli , which can also give you a deeper understanding of Webpack ! I recommend everyone to follow me step by step!

Note in advance: This article only introduces the basic configuration of vue-cli. Regarding optimization and specification, I will write two more articles to explain it later.

1. Create a folder

Create a new folder my-vue-cli to store the project

2. Initialize npm

type in the terminal

npm init

Then just press Enter, so that the project can have an npm management environment, and then we can install the packages we need on this environment.

3、webpack、webpack-cli

install webpack、webpack-cli

  • webpack : packaged tools
  • webpack-cli : Tool to provide command line for webpack

    npm i webpack webpack-cli -D

4、src、public

Create two new folders, src、public , in the root directory. The former is used to place the main code of the project, and the latter is used to place the public static resources of the project.

  • public/index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>my-vue-cli</title>
    </head>
    <body>
    <div id="app"></div>
    </body>
    </html>
  • src/main.js

    import { add } from './tools/add.js'
    
    console.log(add(1, 2))
    console.log('我是main.js')
  • src/tools/add.js

    export const add = (a, b) => {
    return a + b
    }

5. Entry file

The main.js just now is our entry file, which is equivalent to the root node of the entire reference tree. Webpack packaging needs to start searching from the entry file until all reference files are packaged.

Configure the entry file and create a new webpack.config.js in the root directory:

const path = require('path')

module.exports = {
  // 模式 开发模式
  mode: 'development',
  // 入口文件 main.js
  entry: {
    main: './src/main.js'
  },
  // 输出
  output: {
    // 输出到 dist文件夹
    path: path.resolve(__dirname, './dist'),
    // js文件下
    filename: 'js/chunk-[contenthash].js',
    // 每次打包前自动清除旧的dist
    clean: true,
  }
}

6. Configure the packaging command

Configure the packaging command in package.json :

"scripts": {
    "build": "webpack"
},

Now we enter npm run build into the terminal, and we can find that the packaging is successful:

But this is actually not our purpose. Our purpose is to insert the final packaged js file into the index.html just now, because the js file must be referenced by the html file, which makes sense! So we not only have to package js, but also package html

Little knowledge: loader and plugin

  • loader : Enable webpack to parse non-js files, such as css, png, ts, etc.
  • plugin : Expand the packaging function of webpack, such as optimizing the volume, displaying the progress bar, etc.

7. Package html

Packaging html needs to use the plugin html-webpack-plugin , which is plugin, so you need to install it:

npm i html-webpack-plugin -D

And it needs to be configured in webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  // 刚刚的代码...
  
  // 插件都放 plugins 中
  plugins: [
    new HtmlWebpackPlugin({
      // 选择模板 public/index.html
      template: './public/index.html',
      // 打包后的名字
      filename: 'index.html',
      // js文件插入 body里
      inject: 'body',
    }),
  ]
}

Now we can execute the package command npm run build in the terminal and we can see that the html is packaged, and the packaged html is automatically imported into the packaged js file

Now we can open the packaged index.html and find that the console can output, indicating success!

Packaging CSS

Create a new folder src under styles to store style files

  • src/styles/index.scss

    body {
    background-color: blue;
    }

    Then we introduce it in the entry file main.js

    import './styles/index.scss'
    
    // 刚刚的代码...

    Our purpose is to package the file index.scss and let index.html automatically import the packaged css file, so we need to install the following things:

  • sass、sass-loader : Can convert scss code to css
  • css-loader : Enable webpack to pack css
  • sass-resources-loader : optional, supports packaging global public scss files
  • mini-css-extract-plugin : css code can be packaged into a single css file

Let's install these plugins

npm i 
sass
sass-loader
sass-resources-loader
mini-css-extract-plugin
-D

Then configure webpack.config.js

// 刚才的代码...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
  // 刚才的代码...
  plugins: [
    // 刚才的代码...
    new MiniCssExtractPlugin({
      // 将css代码输出到dist/styles文件夹下
      filename: 'styles/chunk-[contenthash].css',
      ignoreOrder: true,
    }),
  ],
  module: {
    rules: [
      {
        // 匹配文件后缀的规则
        test: /\.(css|s[cs]ss)$/,
        use: [
          // loader执行顺序是从右到左
          MiniCssExtractPlugin.loader,
          'css-loader',
          'sass-loader',
          // {
          //   loader: 'sass-resources-loader',
          //   options: {
          //     resources: [
          //       // 放置全局引入的公共scss文件
          //     ],
          //   },
          // },
        ],
      },
    ]
  }
}

At this point, we re-execute the packaging command npm run build , and we can find that the packaged css file appears, and the css file is automatically introduced in index.html :

We can look at the page, and we can see that the background of the body has turned blue, indicating that it has an effect:

package pictures

0622ff33111aeb has been abandoned in url-loader , and asset-module can be used to package images. We first place an image in src/assets/images :

And rewrite index.css

body {
  width: 100vw;
  height: 100vh;
  // 引入背景图片
  background-image: url('../assets/images/guang.png');
  background-size: 100% 100%;
}

Then we add the configuration of the packaged image in webpack.config.js

  module: {
    rules: [
      // 刚刚的代码...
      {
        // 匹配文件后缀的规则
        test: /\.(png|jpe?g|gif|svg|webp)$/,
        type: 'asset',
        parser: {
          // 转base64的条件
          dataUrlCondition: {
             maxSize: 25 * 1024, // 25kb
          }
        },
        generator: {
          // 打包到 dist/image 文件下
         filename: 'images/[contenthash][ext][query]',
        },
     }
    ]
  }

We now re-run npm run build and find that the folder images already exists under dist

Let's take a look at the page background image has taken effect, indicating that the packaging is successful!

configure babel

babel can convert the high-level grammar in our project into a relatively low-level grammar, for example, ES6 can be converted to ES5 , which is compatible with some low-level browsers, so it is necessary

First install the required packages:

  • @babel/core、babel-loader : tool for converting syntax
  • @babel/preset-env : A set of ready-made rules for conversion
  • @babel/plugin-transform-runtime : plugin required to convert async/await

    npm i
    @babel/core babel-loader
    @babel/preset-env
    @babel/plugin-transform-runtime
    -D

    Since babel is a syntax conversion for js files, we need to operate on js in webpack.config.js

    module: {
      rules: [
        // 刚刚的代码...
        {
          // 匹配js后缀文件
          test: /\.js$/,
          // 排除node_modules中的js
          exclude: /node_modules/,
          use: [
            'babel-loader'
          ],
        }
      ]
    }

    It is not enough to configure babel-loader alone, we also need to configure the rules for babel conversion, so we need to create babel.config.js in the root directory

    // babel.config.js
    
    module.exports = {
    presets: [
      // 配置规则
      "@babel/preset-env"
    ],
    // 配置插件
    plugins: ["@babel/plugin-transform-runtime"]
    }

    At this point, we re-run the package npm run build , and we can find that in the packaged js code, the ES6 syntax in the code just now has been converted to ES5 syntax! You can see that const in the code just now has been converted to ES5 syntax

Packaging Vue

Packaging Vue requires the following packages:

  • vue : Dependencies required for Vue development
  • vue-loader : loader for parsing .vue files
  • vue-template-compiler : A tool for parsing templates in vue
  • @vue/babel-preset-jsx : support parsing jsx syntax in vue
Note: vue and vue-template-compiler versions need to be consistent, here I use the 2.6.14 version, vue-loader here I use the 15.9.8 version

So let's install it first:

npm i 
vue@2.6.14 vue-template-compiler@2.6.14
vue-loader@15.9.8 @vue/babel-preset-jsx
-D

Then we need to go to webpack.config.js to configure the parsing of the .vue file

// 刚才的代码...
const { VueLoaderPlugin } = require('vue-loader')

module.exports = {
  // 刚才的代码...
  plugins: [
    // 刚才的代码...
    new VueLoaderPlugin()
  ],
  module: {
    rules: [
      // 刚才的代码...
      {
        test: /\.vue$/,
        use: 'vue-loader',
      }
    ]
  }
}

And configure it in babel.config.js to let webpack support the jsx syntax in the .vue file

module.exports = {
  presets: [
    "@babel/preset-env",
    // 支持vue中的jsx语法
    "@vue/babel-preset-jsx"
  ],
  plugins: ["@babel/plugin-transform-runtime"]
}

Now we can create a new one under src App.vue

<template>
  <div class="box">我是App哈哈哈哈</div>
</template>

<script>
export default {}
</script>

<style lang="scss">
.box {
  width: 500px;
  height: 200px;
  color: #fff;
  background-color: #000;
}
</style>

Then rewrite src/main.js

import Vue from 'vue'
import App from './App.vue'

new Vue({
  render: (h) => h(App),
}).$mount('#app')

At this point, we re-run npm run build , we can see the effect of the page, indicating that the packaging is successful!

Configure path aliases

Sometimes there are too many layers of file references, and the references will look ambiguous, such as
../../../../../App.vue , so we can configure the alias alia

module.exports = {
  // 刚才的代码...
  resolve: {
    // 路径别名
    alias: {
      '@': path.resolve('./src'),
      assets: '~/assets',
      tools: '~/tools'
    },
    // 引入文件时省略后缀
    extensions: ['.js', '.ts', '.less', '.vue'],
  },
}

Now the alias configuration is complete:

  • Before configuration: ../../../../../App.vue
  • After configuration: @/App.vue

webpack-dev-server

We just found out that every time the code is changed, it has to be repackaged, which is very cumbersome. Is there any way to change the code and repackage it automatically? This will use webpack-dev-server

npm i webpack-dev-server -D

Configure webpack.config.js in devServer

  devServer: {
    // 自定义端口号
    // port:7000,
    // 自动打开浏览器
    open: true
  },

Then go to package.json and configure the startup command

  "scripts": {
    "build": "webpack",
    "serve": "webpack serve"
  },

At this point, we can start the project by running npm run serve !

Differentiate the environment

We can't configure all the configurations in one webpack.config.js , because we have two environments development (development environment), production (production environment), so we create the build folder in the root directory and create three files

  • webpack.base.js : shared configuration for both environments

    • Ingress, output configuration
    • Handling of various documents
    • progress bar display
    • path alias
  • webpack.dev.js : Unique configuration for development environment

    • webpack-dev-server
    • Different source-map modes
    • different environment variables
  • webpack.prod.js : Unique configuration for production environment

    • Different source-map modes
    • different environment variables

We need to install a merge plugin webpack-merge first, the configuration for the two environments can merge the common configuration

npm i webpack-merge -D

Then we create a new build folder in the root directory, and create a new webpack.base.js、webpack.dev.js、webpack.config.js under this folder

  • webpack.base.js

    // 公共配置
    
    const path = require('path')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const MiniCssExtractPlugin = require('mini-css-extract-plugin')
    const { VueLoaderPlugin } = require('vue-loader')
    module.exports = {
    // 入口文件 main.js
    entry: {
      main: './src/main.js'
    },
    // 输出
    output: {
      // 输出到 dist文件夹
      // 记得改路径
      path: path.resolve(__dirname, '../dist'),
      // js文件下
      filename: 'js/chunk-[contenthash].js',
      // 每次打包前自动清除旧的dist
      clean: true,
    },
    plugins: [
      new HtmlWebpackPlugin({
        // 选择模板 public/index.html
        template: './public/index.html',
        // 打包后的名字
        filename: 'index.html',
        // js文件插入 body里
        inject: 'body',
      }),
      new MiniCssExtractPlugin({
        // 将css代码输出到dist/styles文件夹下
        filename: 'styles/chunk-[contenthash].css',
        ignoreOrder: true,
      }),
      new VueLoaderPlugin()
    ],
    module: {
      rules: [
        {
          // 匹配文件后缀的规则
          test: /\.(css|s[cs]ss)$/,
          use: [
            // loader执行顺序是从右到左
            MiniCssExtractPlugin.loader,
            'css-loader',
            'sass-loader',
            // {
            //   loader: 'sass-resources-loader',
            //   options: {
            //     resources: [
            //       // 放置全局引入的公共scss文件
            //     ],
            //   },
            // },
          ],
        },
        {
          // 匹配文件后缀的规则
          test: /\.(png|jpe?g|gif|svg|webp)$/,
          type: 'asset',
          parser: {
            // 转base64的条件
            dataUrlCondition: {
              maxSize: 25 * 1024, // 25kb
            }
          },
          generator: {
            // 打包到 dist/image 文件下
            filename: 'images/[contenthash][ext][query]',
          },
        },
        {
          test: /\.js$/,
          // 排除node_modules中的js
          exclude: /node_modules/,
          use: [
            'babel-loader'
          ],
        },
        {
          test: /\.vue$/,
          use: 'vue-loader',
        }
      ]
    },
    resolve: {
      // 路径别名
      alias: {
        '@': path.resolve('./src'),
        assets: '~/assets'
      },
      // 引入文件时省略后缀
      extensions: ['.js', '.ts', '.less', '.vue']
    },
    }
  • webpack.dev.js

    // 开发环境
    
    const { merge } = require('webpack-merge')
    const base = require('./webpack.base')
    
    module.exports = merge(base, {
    mode: 'development',
    devServer: {
      open: true,
      // hot: true,
    }
    })
  • webpack.prod.js

    // 生产环境
    
    const { merge } = require('webpack-merge')
    const base = require('./webpack.base')
    
    module.exports = merge(base, {
    mode: 'production'
    })

Then we go to package.json to modify the instructions

  "scripts": {
    "serve": "webpack serve --config ./build/webpack.dev",
    "build": "webpack --config ./build/webpack.prod"
  },

Next we ran these two commands and found that both were successful:

  • npm run build
  • npm run serve

build progress bar

Whether it is starting the project or packaging, the progress bar needs to be displayed, so the progress bar needs to be configured in webpack.base , we need to install the progress bar plugin progress-bar-webpack-plugin

npm i progress-bar-webpack-plugin -D
// webpack.base.js

// 刚刚的代码...
const ProgressBarPlugin = require('progress-bar-webpack-plugin')
const chalk = require('chalk')

module.exports = {
  // 刚刚的代码...
  plugins: [
    // 刚刚的代码...
    new ProgressBarPlugin({
      format: ` build [:bar] ${chalk.green.bold(':percent')} (:elapsed seconds)`,
    })
  ],
  // 刚刚的代码...
}

Now we can see that there will be a progress bar whether starting the project or packaging

source-map

The function of source-map : When the code reports an error, it can quickly locate the error location. All webpack5 source-map modes of can be viewed on the webpack official website: https://webpack.docschina.org/configuration/devtool/#root

Here I use two modes:

  • development : Use eval-cheap-module-source-map mode, can locate the source code location and source code display, suitable for development mode, small size
  • production : Using nosources-source-map , only the source code location can be located, but the source code cannot be displayed, the size is small, suitable for production mode

So we start configuring source-map

  • webpack.dev.js

    // 刚才的代码...
    module.exports = merge(base, {
    // 刚才的代码...
    devtool: 'eval-cheap-module-source-map'
    })
  • webpack.prod.js

    // 刚才的代码...
    module.exports = merge(base, {
    // 刚才的代码...
    devtool: 'nosources-source-map'
    })

environment variable

Configure the environment variables of these two environments devlopment、production

  • webpack.dev.js

    // 刚才的代码...
    const webpack = require('webpack')
    
    module.exports = merge(base, {
    // 刚才的代码...
    plugins: [
      // 定义全局变量
      new webpack.DefinePlugin({
        process: {
          env: {
            NODE_DEV: JSON.stringify('development'),
            // 这里可以定义你的环境变量
            // VUE_APP_URL: JSON.stringify('https://xxx.com')
          },
        },
      }),
    ]
    })
  • webpack.prod.js

    // 刚才的代码...
    const webpack = require('webpack')
    
    module.exports = merge(base, {
    // 刚才的代码...
    plugins: [
      // 定义全局变量
      new webpack.DefinePlugin({
        process: {
          env: {
            NODE_DEV: JSON.stringify('prodction'),
            // 这里可以定义你的环境变量
            // VUE_APP_URL: JSON.stringify('https://xxx.com')
          },
        },
      }),
    ]
    })

Optimize and standardize

Regarding optimization and specification, I will write two more articles.

Epilogue

I'm Lin Sanxin, an enthusiastic front-end rookie programmer. If you are motivated, like the front-end, and want to learn the front-end, then we can make friends and fish together haha, touch the fish group, add me, please note [Si No]

image.png


Sunshine_Lin
2.1k 声望7.1k 粉丝