Preface: It is 2020, I feel it is time to learn a wave of Webpack, while I have time recently, I went to school, and so on the official website webpack structure and module about a look, I feel I can start to build a project combat, from 0, step by step to record

Version used: WebPack4.x

1. Plug-ins and Loaders are included

* html:    
  html-webpack-plugin clean-webpack-plugin

* css:    
  style-loader css-loader sass-loader node-sass postcss-loader autoprefixer

* js:      
  babel-loader @babel/preset-env @babel/core @babel/polyfill core-js@2 @babel/plugin-transform-runtime
  @babel/runtime  @babel/runtime-corejs2

* vue:     
  vue-loader vue-template-compiler vue-style-loader vue vue-router axios vuex

* webpack: 

  file-loader url-loader webpack-dev-server webpack-merge copy-webpack-plugin happypack HardSourceWebpackPlugin 
  webpack-bundle-analyzer optimize-css-assets-webpack-plugin  portfinder  FriendlyErrorsPlugin
Copy the code


2. Webpack function

-- generate HMTL template -- delete last dist file -- automatically add browser prefix -- use sass precompiler -- convert ES6,7,8,9 syntax to ES5 -- package files larger than 10k into dist, < 10K convert to Base64 -- VUE compatible -- copy static files -- hot update -- differentiate current environment -- multi-threaded packaging -- cache unchanged modules -- G-zip compression -- get native IP -- Package size analysis -- Compress CSS -- Check whether the port existsCopy the code


3. Implementation steps

1. The early experience

1. Create a new file named webpack-vue 2.cdwebpack-vue npm init -y npm i -D webpack webpack-cli 3. Create a new SRC /main.js file and say console.log('hello,webpack'Json - >scripts, add"build":"webpack src/main.js"5. NPM run build if there is an extra dist file, then the first package is successfulCopy the code

2. The configuration

  1. Create a new build folder and create webpack.config.js
  2. Write the following

const path=require('path')

module.exports = {
    mode:'development',
    entry:path.resolve(__dirname,'.. /src/main.js'// output:{filename:'[name].[hash:8].js', // The packaged name generates an 8-digit numberhash
        path.resolve(__dirname,'.. /dist'}} Then modify package.json ->scripts to:"build":"webpack --config build/webpack.config.js"Then NPM runs buildCopy the code

3. Once we have generated main.js, it is impossible to manually import it in index.html every time, so we need this plugin to automatically import it for us

Install the plug-in first:

npm i -D html-webpack-pluginCopy the code

Create a new public/index.html for the root directory

Modify the webpack. Config. Js:

const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin'Module.exports = {mode:'development'// Development mode entry: path.resolve(__dirname,'.. /src/main.js'// output: {filename:'[name].[hash].js'Path: path.resolve(__dirname,'.. /dist'// Plugins :[new HtmlWebpackPlugin({template:path.resolve(__dirname,'.. /public/index.html')]}})Copy the code

Then the NPM run build will notice that there is more index.html in dist and that it has automatically introduced main.js for us

4. Since the hash is generated differently each time, the new main.js will be packed into the dist folder each time, so we need a plug-in to delete the dist file before packaging

Install the plug-in first:

npm i -D clean-webpack-pluginCopy the code

webpack.config.js

const {CleanWebpackPlugin} = require('clean-webpack-plugin'// Plugins :[new HtmlWebpackPlugin({template:path.resolve(__dirname,'.. /public/index.html')
    }),
    new CleanWebpackPlugin()
 ]
Copy the code

5. We usually put static files that do not need to be packaged in public, which will not be packaged in dist, so we need to use plug-ins to copy these files

Install the plug-in first:

npm i -D  copy-webpack-pluginCopy the code

webpack.config.js

const CopyWebpackPlugin = require('copy-webpack-plugin'Plugins: [new CopyWebpackPlugin({patterns: [{from: path.resolve(__dirname,'.. /public'),
          to: path.resolve(__dirname, '.. /dist'}]})]Copy the code

6. In order for WebPack to recognize CSS, we need to install loader and insert the parsed CSS into the style in index.html

First installation:

npm i -D style-loader css-loaderCopy the code

 webpack.config.js

module.exports = {
    module:{
        rules:[{
          test:/\.css$/,
          use:['style-loader'.'css-loader'}}}Copy the code

7. We can use a precompiler to handle CSS better. I’m using Sass

First installation:

npm install -D sass-loader node-sass

 webpack.config.js

module:{
    rules: [{
        test: /\.scss$/,
        use: ['style-loader'.'css-loader'.'sass-loader'}}Copy the code

8. Automatically add the browser prefix

First installation:

npm i -D postcss-loader autoprefixer

webpakc.config.js

  module: {
      rules: [
        {
          test: /\.scss$/,
          use: ['style-loader'.'css-loader'.'postcss-loader'.'sass-loader'}]} Configure compatible browsers in the root directory. Browserslistrc. Here is my default configuration. Last 2 versions not IE <= 8 postcss.config.js module.exports = {plugins: [require(postcss.config.js module.'autoprefixer'} this package will automatically prefix the browser for youCopy the code

9. The previous style-loader just packages CSS into the style-loader in index.html. If there are too many CSS, this method will not work, so we need to separate and extract CSS

First installation:

npm i -D mini-css-extract-plugin

 webpack.config.js

const MiniCssExtractPlugin = require("mini-css-extract-plugin"// Extract CSS module: {rules: [{test: /\.scss$/,
          use: [MiniCssExtractPlugin.loader, 'css-loader'.'postcss-loader'.'sass-loader'}} plugins: [new MiniCssExtractPlugin({filename:"css/[name].[hash:8].css",
        chunkFilename: "[id].css"})] this will pack the CSS into the CSS fileCopy the code

10. In order to reduce the size of images, fonts, etc., we can use url-loader to convert files smaller than the specified size to Base64, and use file-loader to move files larger than the specified size to the specified location

First installation:

npm i -D file-loader url-loader

 webpack.config.js

 module:{
    rules:[
      {
        test:/\.(jpe? g|png|gif|svg)(\? . *)? Use :[{loader:'url-loader',
          options:{
            limit:10240,
            fallback:{
              loader:'file-loader',
              options:{
                name:'img/[name].[hash:8].[ext]',
                publicPath: '.. / '// to prevent image path error}}}}]},{test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\? . *)? $/, use: [{ loader:'url-loader',
          options: {
            limit: 10240,
            fallback: {
              loader: 'file-loader',
              options: {
                name: 'media/[name].[hash:8].[ext]',
                publicPath: '.. / '}}}}]},{test:/\.(woff2? |eot|ttf|otf)(\? .*)$/, loader:'url-loader',
        options:{
          limit:10240,
          fallback:{
           loader:'file-loader, options:{ name:'fonts/[name].[hash:8].[ext]', publicPath: '../'}}}}]} WebPack will only convert and move the images used in the project and find that the image fonts are also packedCopy the code

11. To be compatible with browsers, we need to convert ES6,7,8,9 to Es5

First installation:

npm install -D babel-loader @babel/preset-env @babel/coreCopy the code
 webpack.config.js

module: {
    rules: [{
        test: /\.js$/,
        use: ['babel-loader'[}]} root directory new.babelrc {"presets": [
    "@babel/preset-env"} Some new apis like Promise,setMaps etc don't support conversion yet, so we need another plugin Babel/Polyfill, but this plugin packs all poly into mian.js, so we need another plugin core-js@2 to load 'NPM I on demand-s@babel/polyfill core-js@2 'modify. Babelrc {"presets": [["@babel/preset-env",
      {
        "modules": false."useBuiltIns": "usage"."corejs": 2."targets": {
          "browsers": [
            "last 2 versions"."ie >= 10"[}}]]} One more question, NPM I @babel/plugin-transform-runtime -d NPM I --save @babel/runtime NPM I --save @babel/runtime-corejs2 modified."presets": [
    "@babel/preset-env"]."plugins": [["@babel/plugin-transform-runtime",
      {
        "helpers": true."regenerator": true."useESModules": false."corejs": 2}]]} This perfectly solves the compatibility problem of the new ES6 APICopy the code

12. Compatible vue

First installation:

 npm i -D vue-loader vue-template-compiler vue-style-loader

 npm i -S vue Copy the code

webpack.config.js

const vueLoaderPlugin=require('vue-loader/lib/plugin') 
function resolve(dir) {
  return path.join(__dirname, '.. ', dir)
}

 module:{
    rules:[{
        test:/\.vue$/,
        use:[vue-loader]
    }]
 },
resolve:{
    alias: {'vue$':'vue/dist/vue.runtime.esm.js'.The '@':path.resolve(__dirname,'.. /src')
    },
    extensions: ['.js'.'.vue'.'.json'],}, plugins:[new vueLoaderPlugin()] So that webPack can recognize the vue fileCopy the code

13. Hot update

First installation:

 npm i -D webpack-dev-serverCopy the code

 wepack.config.js

const webpack = require('webpack')

devServer: {
    compress: true,
    port: 8989,
    hot: true,
    inline: true,
    hotOnly: true// Overlay does not refresh when compilation fails:true// Display error publicPath on browser page when compiling error:'/'// Make sure to add open:true, watchOptions: {// Ignore: /node_modules/, // Wait 1s to listen to the changes before executing the action aggregateTimeout: Poll: 1000}}, plugins: [ new webpack.HotModuleReplacementPlugin(), new webpack.NamedModulesPlugin(), New webpack. NoEmitOnErrorsPlugin ()] in the package. The json inside configuration:"dev":"webpack-dev-server --config build/webpack.config.js"Import Vue from from main.js"vue"
import App from "./App"

new Vue({
    render:h=>h(App)
}).$mount('#app'Create a new APP. Vue in the SRC folder, customize the content and the NPM run dev page will runCopy the code

14. Distinguish between development and production environments

Create webpack.dev.js webpack.prod.js in the build folderCopy the code
Development environment: 1. No need to compress code 2. Hot update 3. Production environment: 1. Compress code 2. Extract CSS files 3. Remove soureMap (according to personal needs)...Copy the code

Here we need to install Webpack-Merge to merge configuration items

First installation:

npm i -D webpack-merge   Copy the code

webpack.dev.js

const webpackConfig = require('./webpack.config')
const merge = require('webpack-merge')
const webpack = require('webpack')

module.exports = merge(webpackConfig, {
  mode: 'development',
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    compress: true,
    port: 8989,
    hot: true,
    inline: true,
    hotOnly: true// Overlay does not refresh when compilation fails:true// Display error publicPath on browser page when compiling error:'/'// Make sure to add open:true,
    watchOptions: {
      // 不监听的文件或文件夹,支持正则匹配
      ignored: /node_modules/,
      // 监听到变化后等1s再去执行动作
      aggregateTimeout: 1000,
      // 默认每秒询问1000次
      poll: 1000
    }
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader'] {},test: /\.css$/,
        use: ['vue-style-loader'.'css-loader'.'postcss-loader'],}, {test: /\.scss$/,
        use: ['vue-style-loader'.'css-loader'.'postcss-loader'.'sass-loader'],
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin(),
    new webpack.NoEmitOnErrorsPlugin()
  ]
})Copy the code

 webpack.prod.js

const webpackConfig = require('./webpack.config')
const merge = require('webpack-merge')
const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin'Dist const MiniCssExtractPlugin = require(dist const MiniCssExtractPlugin = require("mini-css-extract-plugin"// Extract CSSfunction resolve(dir) {
  return path.join(__dirname, '.. ', dir)
}

module.exports = merge(webpackConfig, {
  mode: "production",
  devtool: 'none',
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
          name: 'vendors'.test: /[\\\/]node_modules[\\\/]/,
          priority: -10,
          chunks: 'initial'
        },
        common: {
          name: 'chunk-common',
          minChunks: 2,
          priority: -20,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    }
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader']
        exclude: /node_modules/,
        include: [resolve('src'), resolve('node_modules/webpack-dev-server/client')]}, {test: /\.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: '.. / ',}},'css-loader'.'postcss-loader'],}, {test: /\.scss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: '.. / ',}},'css-loader'.'postcss-loader'.'sass-loader'],
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new MiniCssExtractPlugin({
      filename: 'css/[name].[hash].css',
      chunkFilename: 'css/[name].[hash].css',})]})Copy the code

  webpack.config.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin'// const vueLoaderPlugin = require('vue-loader/lib/plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin') // Copy the filefunction resolve(dir) {
  return path.join(__dirname, '.. ', dir)
}

module.exports = {
  mode: 'development',
  entry: path.resolve(__dirname, '.. /src/main.js'),
  output: {
    filename: 'js/[name].[hash:8].js',
    path: path.resolve(__dirname, '.. /dist'),
    chunkFilename: 'js/[name].[hash:8].js'// Asynchronously load the module publicPath:'/'
  },
  externals: {},
  module: {
    noParse: /jquery/,
    rules: [
      {
        test: /\.vue$/,
        use: [{
          loader: 'vue-loader',
          options: {
            compilerOptions: {
              preserveWhitespace: false}}}]}, {test: /\.(jpe? G | PNG | GIF) $/ I / / picture file use: [{loader:'url-loader',
            options: {
              limit: 10240,
              fallback: {
                loader: 'file-loader',
                options: {
                  name: 'img/[name].[hash:8].[ext]',
                  publicPath: '.. / '}}}}]}, {test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\? . *)? $/, // Media file use: [{loader:'url-loader',
            options: {
              limit: 10240,
              fallback: {
                loader: 'file-loader',
                options: {
                  name: 'media/[name].[hash:8].[ext]',
                  publicPath: '.. / '}}}}]}, {test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i, // 字体
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 10240,
              fallback: {
                loader: 'file-loader',
                options: {
                  name: 'font/[name].[hash:8].[ext]',
                  publicPath: '.. / '
                }
              }
            }
          }
        ]
      }
    ]
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js'.The '@': resolve('src'),
    },
    extensions: ['.js'.'.vue'.'.json'}, // plugins: [new HtmlWebpackPlugin({template: path.resolve(__dirname,'.. /public/index.html')
    }),
    new vueLoaderPlugin(),
    new CopyWebpackPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, '.. /public'),
          to: path.resolve(__dirname, '.. /dist'}]})]}Copy the code

3. Webpack configuration optimization

1. Set the mode

Default to Production, webpack4.x compresses code and removes useless code by default

Optional parameters: Production, development

Ps: BEFORE, I thought that the plug-in for compressing CSS and JS would not need to be used if the mode was set to production, but it turned out that I was wrong. After careful comparison, I still need to install it

Install the CSS package first:

npm i -D optimize-css-assets-webpack-pluginCopy the code
webpack.prod.js

const optimizeCss = require('optimize-css-assets-webpack-plugin');

plugins:[
  new optimizeCss({
      cssProcessor: require('cssnano'), // introduce the cssnano configuration compression option cssProcessorOptions: {discardComments: {removeAll:true }
      },
      canPrint: true// Whether to print plug-in information to console})]Copy the code

Compression JS and JS packaging multithreading has not been added, in the online search some said do not add, some said or to install plug-ins, such as the actual project AFTER I run out to add

2. Narrow your search

 aliasWe can tell Webpack to specify folders to find, include exclude and filter noParse as much as possible when we use import JQ from in our code'jquery'Webpack resolves whether the JQ library has dependencies on other packages. This tells WebPack not to parse the extensions that are used frequently in the frontCopy the code

3. Switching from single thread to multi-thread

When webpack processes text, images, and CSS,HappyPack can split the task into multiple sub-threads, which will send the results to the main thread, thus speeding up the packaging speed

First installation:

npm i -D happypack

webpack.prod.js

const HappyPack = require('happypack'// const OS = require('os')
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length })

module: {
    rules: [{
        test: /\.js$/,
        use: ['happypack/loader? id=happyBabel'],
        exclude: /node_modules/,
        include: [resolve('src'), resolve('node_modules/webpack-dev-server/client')]
    }]
}

plugins:[
   new HappyPack({
      id: 'happyBabel',
      loaders: ['babel-loader? cacheDirectory'],
      threadPool: happyThreadPool
    })
]
Copy the code

4. Optimization of third-party modules

We can use DllPlugin DllReferencePlugin to remove the third-party dependencies that do not change much from the dependencies, so that each packaging does not need to package these files, speeding up the packaging speed;

However, the performance of Webpack 4.x is already very good, and the reference to vue-CLI does not use DLL extraction, so we will not use it here, instead we will use another plug-in: Hard-source-webpack-plugin: this plugin compares the configurations that have been changed and only packs the configurations that have been changed. The first time the package speed is normal and the second time the package speed is increased by 50%+

npm i -D hard-source-webpack-plugin

webpack.prod.js

const HardSourceWebpackPlugin = require('hard-source-webpack-plugin'Plugins: [new HardSourceWebpackPlugin()]Copy the code

5.externals

Dependencies loaded via CDN can be set here and will not be compiled via Webpack

6. G – zip

G-zip compression can compress js and CSS again, reducing the package size, requiring nGINx configuration

npm i -D compression-webpack-plugin

webpack.prod.js

const CompressionWebpackPlugin = require('compression-webpack-plugin')
const productionGzipExtensions = ["js"."css"];

plugins:[
   new CompressionWebpackPlugin({
      filename: '[path].gz[query]',
      algorithm: 'gzip'.test: new RegExp("\ \. (" + productionGzipExtensions.join("|") + "$"), threshold: 10240, // Only resources larger than 10K will be processed minRatio: 0.6 // Compression ratio, 0 ~ 1})]Copy the code

7. Automatically obtains the local Ip address and starts the project using the local Ip address

webpack.dev.js

const os = require('os')
devServer:{
  host:()=>{
      var netWork = os.networkInterfaces()
      var ip = ' '
      for (var dev in netWork) {
          netWork[dev].forEach(function (details) {
              if (ip === ' ' && details.family === 'IPv4' && !details.internal) {
                 ip = details.address
                 return; }})}return ip || 'localhost'}}Copy the code

8. Remove some common configurations

The root directory of vue.config.js is created to configure some common configurations such as:

const os = require('os'Module. Exports = {dev: {host: getNetworkIp(), // port: 8999, autoOpen:true, / / automatically open}, build: {productionGzipExtensions:"js"."css"], // you need to enable g-zip file suffix productionGzip:false// Whether to enable G-zip compression}} // Obtain the local IP addressfunction getNetworkIp() {
  var netWork = os.networkInterfaces()
  var ip = ' '
  for (var dev in netWork) {
    netWork[dev].forEach(function (details) {
      if (ip === ' ' && details.family === 'IPv4' && !details.internal) {
        ip = details.address
        return; }})}return ip || 'localhost'} then webpack.dev.js webpack.prod.js imports the file and gets the configurationCopy the code

9. Package size analysis

First installation:

npm i -D webpack-bundle-analyzer

webpack.prod.js

if (process.env.npm_config_report) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer'Push (new BundleAnalyzerPlugin())} Then NPM run build --report will pop up a page, Inside is the package size analysisCopy the code

10. Complete VUE project (VUE-Router AXIos Vuex, etc.)

First installation:

npm i -S vue-router axios vuex

Then in SRC, create a new folder -> Router folder -> create index.js

index.js

import Vue from "vue"
import Router from "vue-router"

Vue.use(Router)

export default new Router({
  mode: 'hash',
  routes: [
    {
      path: '/',
      name: 'home',
      component: () => import(/* webpackChunkName: "home"* /"@/views/home"),
    },
  ]
})

main.js

import router from "./router"
new Vue({
  router,
  render: h => h(App)
}).$mount('#app'Create new views -> home.vue and write something, then NPM run dev will complete a routeCopy the code

At this point, webpack builds vue and the project is complete. We will use this Webpack-vue to write the project

To be continued…

The complete configuration has been uploaded to Github, you can see it yourself if you need it

Making: github.com/biubiubiu01…