Webpack 5 is about to be released. Haven’t you used 4 yet?

Time:2020-8-21

Webpack 5 is about to be released. Haven't you used 4 yet?

introduction

Webpack5 is expected to be released in early 2020. It has been focused on alpha version before. This update focuses on long-term caching, tree shaking and ES6 packaging. Specific changes can be referred to https://github.com/webpack/ch… 。

Webpack is the most popular module packaging tool in modern front-end development. It can complete the loading and packaging of modules only through simple configuration. How can it easily build code by configuring some plug-ins?

This article will not discuss the content to be updated in webpack 5. I believe most front-end students only have simple configuration for webpack, and now such as Vue CLI and UMI have good encapsulation for webpack, but in fact, this is not very good for us. Especially when you want to do some personalized customization for business scenarios. Only by understanding the details of webpack, can we be more comfortable. This paper will take you to build the ultimate front-end development environment step by step from the existing large version of webpack 4.

Several ways to install webpack

  • global(global): throughwebpack index.jsfunction
  • local(project dimension installation): throughnpx webpack index.jsfunction

To avoid the global installation of webpack (for scenarios where different versions of webpack are used for multiple projects), you can usenpx

Entry

Single entrance

// webpack.config.js

const config = {
  entry: {
    main: "./src/index.js"
  }
};

Multi entry

// webpack.config.js

const config = {
  entry: {
    main: "./src/index.js",
    sub: "./src/sub.js"
  }
};

Output (output)

Default configuration

// webpack.config.js
const path = require('path');
...

const config = {
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};

module.exports = config;

Multiple entry points

If the configuration creates multiple separate “chunks” (for example, using multiple entry starting points or using plug-ins like Commons chunkplugin), you should use substitutions to ensure that each file has a unique name.

// webpack.config.js
const path = require('path');
{
  entry: {
    main: './src/index.js',
    sub: './src/sub.js'
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  }
}

//Write to hard disk:. / dist/ main.js , ./dist/ sub.js

Advanced

Using CDN

// webpack.config.js
const path = require('path');
{
  entry: {
    main: './src/index.js',
    sub: './src/sub.js'
  },
  output: {
    publicPath: 'http://cdn.example.com'
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist')
  }
}

//Write to http://cdn.example.com/main.js , http://cdn.example.com/sub.js

loaders

Webpack can use loader to preprocess files. This allows you to package any static resource except JavaScript.

file-loader

  • The file loader can parse the URL in the project and import it (not limited to CSS). According to our configuration, we can copy the image to the corresponding path, and then modify the file reference path after packaging according to our configuration to make it point to the correct file.
  • By default, the file name of the generated file is the MD5 hash of the file content and retains the original extension of the referenced resource.
rules: [
  {
    test: /\.(jpg|png|gif)$/,
    use: {
      loader: "file-loader",
      options: {
        name: "[name]_[hash].[ext]",
        outputPath: "images/"
      }
    }
  }
];

url-loader

  • The URL loader function is similar to file loader, but can return a dataurl when the file size (in bytes) is below the specified limit.
  • URL loader converts resource files into URLs, and file loader has the same function. The difference is that the URL loader is more flexible. It can convert small files into URLs in Base64 format, thus reducing the number of network requests. URL loader depends on file loader.
rules: [
  {
    test: /\.(jpg|png|gif)$/,
    use: {
      loader: "url-loader",
      options: {
        name: "[name]_[hash].[ext]",
        outputPath: "images/",
        limit: 204800
      }
    }
  }
];

css-loader

  • Only responsible for loading the CSS module, and does not apply the loaded CSS style to HTML
  • Importloaders are used to specify the number of loaders to be applied before the CSS loader
  • The query parameter modules enables the CSS module specification
module: {
  rules: [
    {
      test: /\.css$/,
      use: ["style-loader", "css-loader"]
    }
  ];
}

style-loader

  • Responsible for dynamically adding the CSS style loaded by CSS loader to the HTML head style tag
  • It is generally recommended to use style loader in combination with CSS loader

sass-loader

install

yarn add sass-loader node-sass webpack --dev

  • Node sass and webpack are peer dependency of SASS loader, so they can be controlled precisely.
  • Loader execution order: from bottom to top, from right to left
  • By chaining style loader and CSS loader with sass loader, you can immediately apply styles to DOM elements.
// webpack.config.js
module.exports = {
...
module: {
  rules: [{
    test: /\.scss$/,
    use: [{
        Loader: "style loader" // generate JS string as style node
    }, {
        Loader: "CSS loader" // convert CSS into commonjs module
    }, {
        Loader: "sass loader" // compile sass into CSS
    }]
  }]
}
};

postcss-loader

  • In webpack4, postcss loader is used instead of autoprefixer to add browser prefix to CSS3 style. For details, please refer tohttps://blog.csdn.net/u014628388/article/details/82593185
// webpack.config.js
 {
  test: /\.scss$/,
  use: [
    'style-loader',
      'css-loader',
      'sass-loader',
      'postcss-loader'
    ],
}

//postcss.config.js
module.exports = {
    plugins: [
        require('autoprefixer')({ browsers: ['last 2 versions'] }),
    ],
};

plugins

Plugin can help you do something when webpack runs to a certain point

HtmlWebpackPlugin

  • Htmlwebpackplugin will automatically generate an HTML file after the package is finished, and automatically import the JS generated by packaging into this HTML file
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
...
plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html'
    }),
  ],
};

clean-webpack-plugin

  • The clean webpack plugin is used to remove the residual package files. Especially after the hash is added at the end of the file, the file name will be different and the content will be more and more when the file content is changed and repackaged.
  • The clean webpack plugin in the new version only accepts one object and does not need to pass any parameters by default. For details, please refer tohttps://blog.csdn.net/qq_23521659/article/details/88353708
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
...
plugins: [
    new CleanWebpackPlugin()
  ],
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }

SplitChunksPlugin

  • Specific concepts can be referred tohttps://juejin.im/post/5af15e895188256715479a9a
splitChunks: {
    chunks: "async",
    minSize: 30000,
    minChunks: 1,
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    automaticNameDelimiter: '~',
    name: true,
    cacheGroups: {
        vendors: {
            test: /[\/]node_modules[\/]/,
            priority: -10
        },
    default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
        }
    }
}

MiniCssExtractPlugin

The CSS is extracted as an independent file plug-in, and a CSS file is created for each JS file containing CSS, which supports loading CSS and sourcemap on demand

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      // Options similar to the same options in webpackOptions.output
      // both options are optional
      filename: "[name].css",
      chunkFilename: "[id].css"
    })
  ],
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader
          },
          {
            loader: "css-loader",
            options: {
              Importloaders: 2 // used to specify the number of loaders to be applied before CSS loader
              //Modules: true // querying the parameter modules enables CSS module specification
            }
          },
          "sass-loader",
          "postcss-loader"
        ]
      },
      {
        test: /\.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader
          },
          "css-loader",
          "postcss-loader"
        ]
      }
    ]
  }
};

OptimizeCSSAssetsPlugin

Webpack5 may have built-in CSS compressor. Webpack4 needs to use its own compressor. You can use the optimize CSS assets webpack plugin plug-in. set up optimization.minimizer Override the default provided by webpack and make sure that a JS compressor is also specified

const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = {
  optimization: {
    minimizer: [
      new UglifyJsPlugin({
        cache: true,
        parallel: true,
        sourcMap: true
      }),
      new OptimizeCSSAssetsPlugin({})
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[id].css"
    })
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader"]
      }
    ]
  }
};

devtool

source map

Source map is a kind of mapping between package generated code and source code, which is mainly for the convenience of locating and troubleshooting problems. Devtool includes Eval, leap, module, inline and source map. For details, please refer to the following documents:https://www.webpackjs.com/configuration/devtool/

  • Development environment reference configuration:'cheap-module-eval-source-map'
  • Production environment reference configuration:'cheap-module-source-map'

webpack-dev-server

Webpack dev server provides a simple web server and can be reloaded in real time. For details, please refer tohttps://www.webpackjs.com/guides/development/#%E4%BD%BF%E7%94%A8-webpack-dev-server

Interface proxy (request forwarding)

Proxy some URLs can be useful if you have a separate backend development server API and want to send API requests under the same domain name. Dev server uses a very powerfulhttp-proxy-middlewareBag. Commonly used for interface request forwarding. Specific referencehttps://www.webpackjs.com/configuration/dev-server/#devserver-proxy

devServer: {
    contentBase: "./dist",
    open: true,
    hot: true,
    hotOnly: true,
    proxy: {
      "/api": {
        target: "https://other-server.example.com",
        pathRewrite: {"^/api" : ""},
        secure: false,
        bypass: function(req, res, proxyOptions) {
          if (req.headers.accept.indexOf("html") !== -1) {
            console.log("Skipping proxy for browser request.");
            return "/index.html";
          }
        }
      }
    }
  },

Solving single page routing problem

When using the HTML5 history API, any 404 response may need to be replaced with index.html
Enable by passing in the following:

historyApiFallback: true;

By passing in an object, such as using the rewrite option, this behavior can be further controlled:

historyApiFallback: {
  rewrites: [
    { from: /^\/$/, to: "/views/landing.html" },
    { from: /^\/subpage/, to: "/views/subpage.html" },
    { from: /./, to: "/views/404.html" }
  ];
}

webpack-dev-middleware

Webpack dev middleware is a wrapper, which can transfer the files processed by webpack to a server. Webpack dev server uses it internally, and it can also be used as a separate package for more customization to meet more requirements

// server.js
//Using webpack dev Middleware
// https://www.webpackjs.com/guides/development/#%E4%BD%BF%E7%94%A8-webpack-dev-middleware
const express = require("express");
const webpack = require("webpack");
const webpackDevMiddleware = require("webpack-dev-middleware");
const config = require("./webpack.config.js");
const complier = webpack(config);

const app = express();

app.use(
  webpackDevMiddleware(complier, {
    publicPath: config.output.publicPath
  })
);

app.listen(3000, () => {
  console.log("server is running");
});

Hot Module Replacement

Hot module replacement (HMR) is one of the most useful functions provided by webpack. It allows various modules to be updated at run time without a full refresh.

// webpack.config.js
...
const webpack = require('webpack');
...
devServer: {
  contentBase: './dist',
  open: true,
  hot: true,
  hotOnly: true
},
plugins: [
  ...
  new webpack.HotModuleReplacementPlugin()
],

If hot module replacement has been enabled through hotmodulereplacementplugin, its interface will be exposed in the module.hot Property. Usually, users check whether the interface is accessible before they start using it.

// index.js
if (module.hot) {
  module.hot.accept("./library.js", function() {
    //Use the updated library module to perform some operations
  });
}

Bundle analysis

With the help of some official visual analysis tools, the packaged modules can be analyzed and optimized

  • webpack-chart: webpack data interaction pie chart
  • webpack-visualizer: visualize and analyze your bundle to see which modules take up space and which may be reused
  • webpack-bundle-analyzer: a plug-in and cli tool for analyzing bundle content, which is presented to users in a convenient, interactive and scalable tree view

Preloading、Prefetching

Prefetch: wait until the core code is loaded, and then load the file corresponding to prefetch when the page bandwidth is idle; preload: load the file with the main file

  • You can use Google browser coverage tool to view code coverage (Ctrl + Shift + P > show coverage)
  • The use of asynchronous introduction of JS can improve the usage rate of JS. Therefore, webpack suggests that we use asynchronous import more, which is also the case splitChunks.chunks The default value of is the reason for “async”
  • Use magic notes/_ webpackPrefetch: true _ /In this way, when the main JS is loaded and the bandwidth is idle, the JS to be introduced will be automatically downloaded
  • Use magic notes/_ webpackPreload: true _ /The difference is that webbackprefetch will wait until the main service file is loaded and the bandwidth is free before downloading JS. However, preload is loaded together with the main service file

babel

Babel compiles ES6, JSX, etc

  • @Babel / core Babel core module
  • @Babel preset env compiler ES6, etc
  • @Babel / preset react transform JSX
  • @Babel / plugin transform runtime avoids Polyfill from polluting global variables and reduces packaging volume
  • @Babel / Polyfill ES6 built in method and function conversion gasket
  • @babel/runtime
module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: "babel-loader"
      }
    }
  ];
}

Create a new. Babelrc file

{
  "presets": ["@babel/preset-env", "@babel/preset-react"],
  "plugins": ["@babel/plugin-transform-runtime"]
}

On demand introduction of Polyfill

Under SRC index.js The @ Babel / Polyfill is introduced globally and the ES6 syntax is written in. However, this has one disadvantage
The global introduction of @ Babel / Polyfill may import unnecessary Polyfill in the code, thus making the packing volume larger and modifying the. Babelrc configuration


`yarn add [email protected] @babel/runtime-corejs2 --dev`

{
  "presets": [
    [
      "@babel/preset-env", {
      "useBuiltIns": "usage"
      }
    ],
    "@babel/preset-react"
  ],
  "plugins": ["@babel/plugin-transform-runtime"]
}

This configures the on-demand introduction. After configuring the introduction of Polyfill on demand, Babel will automatically import the relevant Polyfill when using functions above ES6, which can greatly reduce the volume after package compilation.

Differences between Babel runtime and Babel Polyfill

reference resourceshttps://www.jianshu.com/p/73ba084795ce

  • Babel Polyfill will “load the entire Polyfill library”, handle the new API in the compiled code, and insert some help functions into the code
  • Babel Polyfill solves the problem that Babel does not convert the new API. However, inserting help functions directly into the code will pollute the global environment, and different code files contain duplicate code, resulting in larger code volume after compilation. In order to solve this problem, Babel provides a separate package Babel runtime to provide tool functions for compiling modules. After the Babel plugin transform runtime is enabled, Babel will use the tool functions under Babel runtime
  • Babel runtime is suitable for component and class library projects, while Babel Polyfill is suitable for business projects.

Advanced concepts

tree shaking(js)

Tree shaking can clear the useless JS code in the code. It only supports the import mode, but not the common JS mode
Mode does not need to be configured for production. The following configuration is for development

// webpack.config.js
optimization: {
  usedExports: true
}


// package.json
"sideEffects": false,

Code Spliting

Code segmentation, independent of webpack

  • Synchronization code (need to be in webpack.config.js Configuration optimization in)
// index.js
import _ from 'lodash';

console.log(_.join(['a','b','c'], '****'))

//In webpack.base.js Do relevant configuration in
optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },
  • Asynchronous code (no configuration required, but installation required@babel/plugin-syntax-dynamic-importBag)
// index.js
function getComponent() {
  return import("lodash").then(({ default: _ }) => {
    const element = document.createElement("div");
    element.innerHTML = _.join(["Jack", "Cool"], "-");
    return element;
  });
}

getComponent().then(el => {
  document.body.appendChild(el);
});

Caching

By using output.filename Replacing the file name can ensure that the browser can get the modified file. [hash] substitution can be used to include a build specific hash in the file name, but a better way is to use the [contenthash] replacement. When the file content changes, the [contenthash] will also change

output: {
  filename: "[name].[contenthash].js",
  chunkFilename: '[name].[contenthash].chunk.js'
}

Shimming

The webpack compiler can identify modules written according to the es2015 module syntax, commonjs or AMD specification. However, some third-party libraries may refer to some global dependencies (such as & dollar; in jQuery). These libraries may also create global variables that need to be exported. These “non compliant modules” are where shimming works

  • Shimming global variable (third party Library) (provideplugin is equivalent to a gasket)
 const path = require('path');
+ const webpack = require('webpack');

  module.exports = {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
-   }
+   },
+   plugins: [
+     new webpack.ProvidePlugin({
+       _: 'lodash'
+     })
+   ]
  };
  • Fine grained shimming (this points to window)
 const path = require('path');
  const webpack = require('webpack');

  module.exports = {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
+   module: {
+     rules: [
+       {
+         test: require.resolve('index.js'),
+         use: 'imports-loader?this=>window'
+       }
+     ]
+   },
    plugins: [
      new webpack.ProvidePlugin({
        join: ['lodash', 'join']
      })
    ]
  };

environment variable

The webpack command-line Environment Option — env allows you to pass in any number of environment variables. Your environment variables will be accessible webpack.config.js 。 For example– env.production Or– env.NODE_ ENV=local

webpack --env.NODE_ENV=local --env.production --progress

Using the environment variable, you must make a change to the webpack configuration. Usually, module.exports Points to the configuration object. To use this env variable, you must convert module.exports Is the function:

// webpack.config.js
const path = require("path");

module.exports = env => {
  // Use env.<YOUR VARIABLE> here:
  console.log("NODE_ENV: ", env.NODE_ENV); // 'local'
  console.log("Production: ", env.production); // true

  return {
    entry: "./src/index.js",
    output: {
      filename: "bundle.js",
      path: path.resolve(__dirname, "dist")
    }
  };
};

Library packaging configuration

In addition to packaging application code, webpack can also be used to package JavaScript libraries
Users should be able to access the library in the following ways:

  • Es2015 module. For example, import library from ‘Library’
  • Commonjs module. For example, require (‘library ‘)
  • Global variables, when introduced by script script

We may use third-party libraries such as library in dallash. Now, if you execute webpack, you will find that a very large file has been created. If you look at this file, you will see that lodash is also packaged into the code. In this scenario, we prefer to treat lodash as peer dependency. In other words, users should have installed lodash. Therefore, you can relinquish control of the external library and give control to users who use the library. This can be done using externals configuration:

  // webpack.config.js
  var path = require('path');

  module.exports = {
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'webpack-numbers.js'
-   }
+   },
+   externals: {
+     lodash: {
+       commonjs: 'lodash',
+       commonjs2: 'lodash',
+       amd: 'lodash',
+       root: '_'
+     }
+   }
  };

For the library of wide use, we hope that it can be compatible with different environments, such as commonjs, AMD, Node.js Or as a global variable. In order to enable users to add various attributes of Library output in the library environment:

  // webpack.config.js
  var path = require('path');

  module.exports = {
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
-     filename: 'library.js'
+     filename: 'library.js',
+     library: 'library'
    },
    externals: {
      lodash: {
        commonjs: 'lodash',
        commonjs2: 'lodash',
        amd: 'lodash',
        root: '_'
      }
    }
  };

When you import modules, this exposes your library bundle as a global variable called webbacknumbers. To make library compatible with other environments, you also need to add the librarytarget attribute to the configuration file. This is an option to control how the library is exposed in different ways.

  var path = require('path');

  module.exports = {
    entry: './src/index.js',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'library.js',
+     library: 'library',
+     libraryTarget: 'umd'
    },
    externals: {
      lodash: {
        commonjs: 'lodash',
        commonjs2: 'lodash',
        amd: 'lodash',
        root: '_'
      }
    }
  };

We also need to set up package.json Add the file path to generate the bundle.

// package.json
{
  ...
  "main": "dist/library.js",
  ...
}

PWA package configuration

Progressive web application (PWA) is a kind of web app which can provide the experience similar to native app. PWA can be used to do a lot of things. The most important of these is that the application can continue to run functions while offline. This is achieved by using a network technology called service workers
Add the workbench webpack plugin plug-in and adjust it webpack.config.js Document:

npm install workbox-webpack-plugin --save-dev

webpack.config.js

const path = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
  const CleanWebpackPlugin = require('clean-webpack-plugin');
+ const WorkboxPlugin = require('workbox-webpack-plugin');

  module.exports = {
    entry: {
      app: './src/index.js',
      print: './src/print.js'
    },
  plugins: [
    new CleanWebpackPlugin(['dist']),
    new HtmlWebpackPlugin({
-     title: 'Output Management'
+     title: 'Progressive Web Application'
-   })
+   }),
+   new WorkboxPlugin.GenerateSW({
+// these options help Serviceworkers quickly enable
+// no legacy of "old" Serviceworkers is allowed
+     clientsClaim: true,
+     skipWaiting: true
+   })
  ],
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };

Register for service worker

  import _ from 'lodash';
  import printMe from './print.js';

+ if ('serviceWorker' in navigator) {
+   window.addEventListener('load', () => {
+     navigator.serviceWorker.register('/sw.js').then(registration => {
+       console.log('SW registered: ', registration);
+     }).catch(registrationError => {
+       console.log('SW registration failed: ', registrationError);
+     });
+   });
+ }

Now let’s test it. Stop the server and refresh the page. If the browser can support service worker, you should see that your application is still running normally. However, the server has stopped serving, and it is the service worker who is providing the service.

Typescript packaging configuration

For referencehttps://www.webpackjs.com/guides/typescript/orhttps://webpack.js.org/guides/typescript/

  • Install TS dependenciesnpm install --save-dev typescript ts-loader
  • Increase tsconfig.json configuration file
{
  "compilerOptions": {
    "outDir": "./dist/",
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "jsx": "react",
    "allowJs": true
  }
}
  • webpack.config.js Add TS / TSX syntax support (TS loader)
const path = require("path");

module.exports = {
  entry: "./src/index.ts",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: "ts-loader",
        exclude: /node_modules/
      }
    ]
  },
  resolve: {
    extensions: [".tsx", ".ts", ".js"]
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "dist")
  }
};
  • When installing a third-party library from NPM, be sure to remember to install the type declaration file of the library at the same time. Type declaration files for these third-party libraries can be found and installed from typesearch. asnpm install --save-dev @types/lodash

Performance optimization of webpack

  • Update the versions of node, yarn, webpack, etc
  • Apply loader on as few modules as possible
  • Make plugins as simple as possible and ensure reliability (select plug-ins verified by the community)
  • Reasonable configuration of resolve parameter (refer tohttps://www.webpackjs.com/configuration/resolve/)
  • Using dllplugin to improve packing speed
  • Control package file size (tree shaping / splitchunksplugin)
  • Thread loader, parallel webpack, happypack
  • Rational use of sourcemap
  • combinationstats.jsonPackage analysis
  • Development environment memory compilation
  • Elimination of useless plug-ins in development environment

last

You can focus on my official account, front end forest. I will issue some front-end articles related to the large front end and actual combat summary in the daily development process. Of course, I’m also an active contributor to the open source community, GitHub addresshttps://github.com/Jack-coolWelcome star!!!

Webpack 5 is about to be released. Haven't you used 4 yet?