Webpack performance optimization

In the previous article, the basic WebPack environment was set up and you were happy to write code on it.

But that’s not enough. The above configuration is just the most basic configuration. We also want to do some optimizations. Optimization has two aspects:

  • Optimize the development experience
  • Optimize output quality

In order to optimize the development experience, Webpack provides us with the following solutions:

  1. Use the source-map feature to view the source code for the runtime
  2. Use WebpackDevServer to implement automatic browser refresh
  3. Reduce file scope and optimize build speed
  4. CSS support: Supports less, SASS, and automatic completion of the browser prefix PostCSS
  5. Optimize file listening range
  6. DllPlugin and HardSourceWebpackPlugin optimize build performance
  7. [heavy] Use happypack to execute packaging tasks concurrently

To optimize the output quality, WebPack gives us the following solution:

  1. Extract and compress the CSS
  2. Compressed HTML
  3. 【 近 】 Shaking, Shaking, Shaking
  4. Code Splitting

Let’s see what the configuration looks like. (Complete code file for the configuration is available at the end of the article.)

Optimize the development experience

source-map

During debugging, we need to use the devtool feature in WebPack for a better experience. It has a configurable Souce-Map feature that contains the mapping between source code and packaged code, which can be located via sourceMap.

In Devtool, there are several options:

  • Eval: Wrap module code in eval
  • The source – the map: produce.mapfile
  • Cheap: Does not contain column information
  • Module: includes third-party modules, including sourceMap of loader
  • The inline:.mapEmbedded as DataURI and not generated separately.mapfile

Recommended configurations:

//webpack.config.js
devtool: "cheap-module-eval-source-map"// Development environment configuration

// Enable is not recommended online
Copy the code

WebpackDevServer

Every time you change the code, you have to repackage it, open the browser, and refresh it. We can improve this experience by installing webpackDevServer.

In addition, the proxy mode can be used to proxy requests from the backend to the local server or test server, facilitating self-testing during commissioning.

  • The installation

    npm i webpack-dev-server -D
    Copy the code
  • configuration

    Modify the package. The json:

    "scripts": {
    	"server": "webpack-dev-server"
    }
    Copy the code

    Modify the webpack.config.js configuration:

    devServer: {
        contentBase: "./dist",
        open: true, port: 8081, // the local port of webpack-dev-server proxy: {// Through the proxy, requests starting with/API are not put into webpack-dev-server, but into target"/api": {
              target: "http://localhost:9092"}}}Copy the code
  • Start the

    npm run server
    Copy the code

Narrow file scope

During development, you can reduce webPack’s unnecessary file scans and speed up each package by narrowing the file scope.

Optimize the resolve.modules configuration

Resolve. modules is used to configure the directory in which Webpack looks for third-party modules. The default is [‘node_modules’]. If it doesn’t, it goes up to the directory… /node_modules find, no more will go to.. /.. /node_modules, and so on. If our third party modules are installed in the project root directory, we can specify this path directly.

Optimize the resolve.alias configuration

Resolve. Alias Configures the alias to map the original import path to a new one.

Take react as an example. The React library has two sets of code:

  • CJS: Uses modular code using the commonJS specification

  • Umd: Complete code that is packaged, not modularized, and can be executed directly

By default, Webpack recursively parses and processes dependent files from the entry file./node_modules/bin/react/index. We can specify the file directly to avoid this time consuming area.

Optimize the resolve.extensions configuration

When an import statement does not have a file suffix, WebPack automatically adds the suffix and tries to find out if the file exists. The resolve. Extensions here refer to the suffix that will be attached. It is recommended that the list of suffixes try as few as possible, we only set one here:

extensions:['.js']
Copy the code

Thus, in resolve, the resulting configuration looks like this:

resolve: {
        modules: [path.resolve(__dirname, "./node_modules")].alias: {
            "vue": path.resolve(
              __dirname,
              "./node_modules/vue/dist/vue.esm.js"
            ),
            "react": path.resolve(
                __dirname,
                "./node_modules/react/umd/react.production.min.js"
              ),
              "react-dom": path.resolve(
                __dirname,
                "./node_modules/react-dom/umd/react-dom.production.min.js")},extensions: ['.js']},Copy the code

The CSS support

There are two ways to optimize the CSS development experience:

  • Less or SASS is supported as the CSS stack
  • Use PostCSS to automatically complete browser prefixes for styles.

Both of these can save a lot of time in the development process. I am used to using less, so I hereby install the corresponding loader.

The installation
npm i less less-loader -D
npm i post-css-loader autoprefixer -D
Copy the code
use
// Add loader corresponding to less type in webpack.config.js
      rules: [
            {
                test: /\.less$/.use: ["style-loader"."css-loader"."less-loader"]}]// Create postcss.config.js To configure the browser prefix to be added in postcss
module.exports = {
  plugins: [
    require("autoprefixer") ({overrideBrowserslist: ["last 2 versions"."1%" >] // The type of browser to override}})];Copy the code

Optimize file listening performance

When we debug, we want to turn on file listening mode so that WebPack can take action when it detects file changes. The devServer above has file listening enabled. Of course, we can also start by ourselves, through the command wepack –watch can be achieved.

Under normal circumstances, we do not need to listen on third-party modules, so we can configure the listening mode, not to listen on third-party modules folder. As a result, webPack will consume significantly less memory and CPU.

//webpack.config.js

module.export = {
		watchOptions : {
      ignored : '/node_modules/'}}Copy the code

DllPlugin, HardSourceWebpackPlugin

Again, for third-party library modules. There are many third-party libraries introduced in the project that will not be updated for a long time. So when we pack, we can pack separately to speed up the packing. It only needs to be compiled once and then stored in the specified file (here we can call it a dynamic link library). These modules are not compiled later in the build process, and the corresponding plug-ins are used directly to reference the dynamically linked library code. Therefore, the build speed can be greatly increased.

In essence is to do caching!

The DllPlugin and DllReferencePlugin worked together to accomplish this task. However, the whole configuration process is tedious (if you have experienced, you know. React Vue has ditched the DLL approach). There is now a new plugin called HardSourceWebpackPlugin. It does the same work as the DllPlugin and DllReferencePlugin, but the configuration process is extremely simple:

//webpack.config.js // const HardSourceWebpackPlugin = require('hard-source-webpack-plugin'); // Use new HardSourceWebpackPlugin()Copy the code

The first time it is packaged, it is relatively slow and the corresponding third-party library module needs to be written to the cache. But after the packaging speed increase, the effect is very obvious!!

Happypack executes tasks concurrently

Webpack running on Node is single-threaded and cannot handle multiple tasks at the same time. Happypack allows WebPack to do just that:

Happypack instantiates a Happypack object with new Happypack (), which tells the Happypack core scheduler how to convert a class of files through a series of Loaders, and can specify how to assign child processes to such converters. The logic code of the core scheduler is in the main process, that is, in the process running Webpack, the core scheduler will assign each task to the currently idle sub-process, and the sub-process will send the result to the core scheduler after processing, and the data exchange between them is realized through the interprocess communication API. When the core scheduler receives the result from the child process, it notifies WebPack that the file has been processed.

  • Configuration happyPack
//webpack.config.js
const HappyPack = require("happypack");
const os = require("os");
// Make full use of the multi-core function, set the number of processes to the number of cores on the device
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
Copy the code
  • Set the mapping between Loader and happyPack by ID.
module: {
        rules: [{test: /\.css$/.use: ["happypack/loader? id=css"] {},test: /\.less$/.use: ["happypack/loader? id=less"] {},test:/\.(png|jpe? g|gif)$/.use: ["happypack/loader? id=pic"] {},test: /\.(eot|ttf|woff|woff2)$/.use: ["happypack/loader? id=ttf"] {},test: /\.js$/.include: path.resolve(__dirname, "./src"),
                use: ["happypack/loader? id=babel"]]}},Copy the code
  • To set up more detailed loader configuration information in HappyPackPlugin:
    plugins: [
	        new HappyPack({
            id: "css".loaders: ["style-loader"."css-loader"].threadPool: happyThreadPool
          }),
          new HappyPack({
            id: "less".loaders: ["style-loader"."css-loader"."less-loader"].threadPool: happyThreadPool
          }),
          new HappyPack({
            id: "pic".loaders: [{loader: "file-loader".options: {
                  name: "[name]_[hash:6].[ext]".outputPath: "images/"}}].threadPool: happyThreadPool
          }),
          new HappyPack({
            id: "ttf".loaders: [{loader: "file-loader".options: {
                  name: "[name].[ext]",}}],threadPool: happyThreadPool
          }),
          new HappyPack({
            id: "babel".loaders: [{loader: "babel-loader"}].threadPool: happyThreadPool
          }),
     ]
Copy the code

Optimize output quality

The above configurations are all done to optimize the development experience. After development is complete, we want the quality of our file output to be guaranteed. Simply put, the resulting file size is as small as possible. To achieve this, WebPack gives us some solutions:

Extract and compress the CSS

Without the extraction configuration, our CSS is packaged directly into JS, and we want to be able to generate CSS files separately. Because the CSS file generated separately can be downloaded in parallel with the JS file (during the web page loading process), the file loading efficiency can be improved.

Webpack provides us with the MiniCssExtractPlugin to do CSS stripping. Usage:

/ / installation
npm i mini-css-extract-plugin -D

//webpack.config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module: {
        rules: [{test: /\.less$/.include: path.resolve(__dirname, "./src"),
            use: [
              MiniCssExtractPlugin.loader, // The style-loader is required
              "css-loader"."postcss-loader"."less-loader"]]}},plugins : [
        // Extract CSS as a separate file output
        new MiniCssExtractPlugin({
            filename: "css/[name]_[contenthash:6].css"})]Copy the code

At the same time, there are plug-ins to compress CSS files: optimize- CSS -assets-webpack-plugin, CSsnano. Usage:

/ / installation
npm i cssnano optimize-css-assets-webpack-plugin -D

//webpack.config.js
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");

  plugins: [
  	// Extract CSS as a separate file output
    new MiniCssExtractPlugin({
      filename: "css/[name]_[contenthash:6].css"
    }),
    new OptimizeCSSAssetsPlugin({
      cssProcessor: require("cssnano"), // Introduce the CSsnano configuration compression option
      cssProcessorOptions: {
        discardComments: { removeAll: true } // Remove all unused styles}}),]Copy the code

Compressed HTML

For COMPRESSION of HTML, you can use the plug-in we mentioned in the previous article: htmlWebPackPlugin. Usage:

new htmlwebpackplugin({
      title: "Home page",
      template: "./src/index.html",
      filename: "index.html",
      minify: {
        removeComments: true// collapseWhitespace in HTMLtrue// Delete whitespace and newline characters minifyCSS:true// compress inline CSS}}),Copy the code

JavaScript Tree Shaking

For more information about Tree Shaking, see this article. One key point to understand is this:

In JavaScript, tree-shaking elimination is dependent on ES6 module features. ES6 module dependencies are deterministic, independent of runtime state, and can be reliably statically analyzed, which is the basis for tree-shaking. Static analysis means analyzing code literally without executing it.

ES6 module features:

  • Can only appear as a statement at the top level of a module
  • The module name for import must be a string constant
  • Import binding is immutable

In the latest WebPack 4, Tree Shaking is turned on by default as long as it is set to production mode.

To better serve Tree Shaking, do the following:

  • Using ES6 module syntax (i.eimportexport).
  • Standardize code habits, use more pure functions, write less side – effect code.
  • If there is a specific side effect code (e.g@babel/polyfill) are required in the projectpackage.jsonIn the file, add a “sideEffects” entry.

CSS Tree Shaking

Since we have Tree Shaking in our JavaScript code, isn’t it possible to have a plugin for our CSS code? B: of course.

// install NPM i-d purifycss-webpack Purify - CSS glob-all //webpack.config.js const PurifyCSSPlugin = require("purifycss-webpack");
const glob = require("glob-all"); New PurifyCSSPlugin({paths: glob.sync(path.resolve(__dirname,"./src/*.html"Path.resolve (__dirname,"./src/*.js")])})Copy the code

Code Splitting

Once packaged, all pages generate only one bundle.js (the CSS files have been stripped out separately). At this point, there are two considerations for code splitting:

  • The code is bulky and not easy to download.
  • Poor use of browser download resources (Chrome has a default maximum of 6 HTTP requests at one time)

Before Webpack 4, there were plug-ins for code splitting, such as SplitChunksPlugin and CommonsChunkPlugin. Now, WebPack 4 has this code split work built in. For dynamically imported modules, the new Common Chunk Strategy provided by WebPack V4 + is used by default. We just need to write it in the configuration file.

// default configuration optimization: {splitChunks: {chunks:'async'// minSize: 30000,// Minimum size, when modules are larger than 30KB maxSize: 0,// minChunks are not recommended for secondary segmentation: MaxAsyncRequests: 5, maxInitialRequests: 3,// Maximum initial requests automaticNameDelimiter:'~'// Package split symbol name:true, // packaged names cacheGroups: {// cacheGroups: {test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true}}}}Copy the code

In our case, we use this code to split the configuration like this:

   optimization: {
        usedExports: true.splitChunks: {
            chunks: "all".automaticNameDelimiter: "-".cacheGroups: {
            / / cache group
            lodash: {
                test: /lodash/.name: "lodash".minChunks: 1
            },
            react: {
                test: /react|react-dom/.name: "react".minChunks: 1
            },
            vue: {
                test: /vue/.name: "vue".minChunks: 1}}}}Copy the code

conclusion

Because the above configuration has the difference between the development mode and the production mode, finally made a distinction and merge of its configuration items. Divided into three files:

//webpack.config.js

const merge = require("webpack-merge");
const baseConfig = require("./webpack.config.base.js");
const prodConfig = require("./webpack.config.prod.js");
const devConfig = require("./webpack.config.dev.js");

module.exports = env= > {
  if (env && env.production) {
    return merge(baseConfig, prodConfig);
  } else {
    returnmerge(baseConfig, devConfig); }};Copy the code

The corresponding package.json configuration is:

"scripts": {
    "dev": "webpack"."build": "webpack --env.production"."server": "webpack-dev-server"
  },
Copy the code

Renderings in two modes:

The final configured code can be seen here on demand.


Front-end performance optimization series:

(a) : start with the TCP three-way handshake

(two) : for the blocking of TCP transmission process

(3) : optimization of HTTP protocol

(IV) picture optimization

(v) : browser cache strategy

(vi) : How does the browser work?

(vii) : Webpack performance optimization