As long as it is for project development, must leave the project to optimize the topic, of course, if you do the project is very small, may not meet build problems, nature also not too concerned about performance optimization problem, but in the practical work of project page than the more commonly, as more and more complex business logic, if you are based on the project of Webpack, The build speed will get slower and slower, and we’ll have to think about packaging optimizations, or it’ll take ten minutes to start a project and several minutes to render a code change. This article will take you through the process of optimizing your project using webPack configuration from the following aspects:

  • Extract common code
  • The compression code
  • Tree Shaking
  • Code Splitting

Extract common code

Extracting common code is generally used in multi-entry situations, and is optimized to prevent repeated packaging, with the benefit of reducing file size and speeding up packaging and startup

  • Extract the js common code:

    If each entry file refers to the same module (suppose there is a custom module tools.js and a third-party module lodash), the file code will look like this:

        // a.js
        import _ from 'lodash'
        import tools from './utils/tools.js'
    Copy the code
        // b.js
        import _ from 'lodash'
        import tools from './utils/tools.js'
    Copy the code

    The Webpack configuration is as follows:

        module.exports = {
            / /... Other configuration
            entry: {a:'./src/a.js'.b:'./src/b.js'
            },
            output: {path: path.join(__dirname, "dist"),
                filename:'[name].bundle.js'}}Copy the code

    The package looks like this:

    By default, tools.js and LoDash are packaged twice, which increases file size (both files are 536K), affects performance, and reduces the quality of our code. In this case, we can optimize by extracting common code

    The optimized WebPack configuration is as follows:

    Using optimization. SplitChunks. CacheGroups implementation to extract the common code (more configuration instructions please see: webpack.docschina.org/plugins/spl…

        module.exports = {
            entry: {a:'./src/a.js'.b:'./src/b.js'
            },
            output: {path: path.join(__dirname, "dist"),
                filename:'[name].bundle.js'
            },
            optimization: {splitChunks: {cacheGroups: {/ / note:
                        // The name of the key is custom
                        // priority: A larger value indicates a higher priority
                        // chunks specifies which modules to pack. Optional values are
                        // * initial: initial block
                        // * async: load blocks on demand (default)
                        // * all: all blocks
                        
                        // common: package common code in business (tools.js above)
                        common: {
                          name: "common".// If the package name is not specified, upper-layer key is used as the package name
                          chunks: "all".minSize: 10.priority: 0
                        },
                        // vendor: package the file in node_modules (lodash above)
                        vendor: {
                          name: "vendor".test: /node_modules/,
                          chunks: "all".priority: 10
                        }
                    }
                }
            }
        }
    Copy the code

    As a result, loDash is packaged into vendor.bundle.js, and tools.js is packaged into common.bundle.js, enabling the extraction of common code.

  • Use externals to extract third-party libraries

    Specifically, the externals configuration should tell WebPack which third-party libraries do not need to be packaged into the bundle. In fact, there are some third-party libraries in development (such as jQuery), and if we use the following code in the project, jQuery will be packaged into the final bundle when packaged

        import $ from 'jquery'
    Copy the code

    However, sometimes we prefer to use CDN to introduce jQuery. Externals provided by WebPack can be easily implemented. The steps are as follows:

    1. Introduce CDN library links in HTML files
        <! DOCTYPEhtml>
        <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><%= htmlWebpackPlugin.options.title %></title>
        </head>
        <body>
            <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
        </body>
        </html>
    Copy the code
    1. Webpack configuration
        module.exports = {
            / /... Other options
            externals: {
                jquery: "jQuery",}}Copy the code
    1. Using jQuery
        import $ from 'jquery'
        $('h1').css('color'.'#58bc58')
    Copy the code

    As you can see from the final packaging, WebPack just does a simple export of the jQuery variable in global scope that existed when jQuery was introduced into script

    Similarly, some of our custom toollibraries can be packaged in this way if they don’t need to be packaged into the final bundle.

  • Extract CSS common code

    Css-loader and style-loader just write CSS styles into the style tag of the HTML page. If it is a SPA single-page application, this is ok, but if it is a multi-page application, it will write CSS styles in each page. Can we extract the same parts and use link to import the page? The answer is yes, just use the mini-CSS-extract-plugin

    1. Installing a plug-in

          npm install mini-css-extract-plugin -D
      Copy the code
    2. Configuration webpack

      In the configuration, in addition to configure plugins option, but also need to configure in the loader, because is to extract the CSS into a single file, so delete the old style – loader, to MiniCssExtractPlugin. Loader

          const MiniCssExtractPlugin = require("mini-css-extract-plugin");
      
          module.exports = {
              plugins: [new MiniCssExtractPlugin()],
              module: {
                  rules: [{test: /\.css$/i,
                      use: [MiniCssExtractPlugin.loader, "css-loader"],},],},};Copy the code

The compression code

The realization deletes the redundant code, the annotation, simplifies the code writing method and so on.

  • Compression JS:

    By default, Webpack automatically compresses code in the production environment (mode:’production’). The terser-webpack-plugin is used internally. Please check the official website for details

  • Compress CSS:

    To compress CSS, extract CSS into a separate file (as described earlier) and compress CSS using the CSS-Minimizer-webpack-plugin, which is optimized for CSS code compression based on CSSNano

    1. The installation

          npm install css-minimizer-webpack-plugin -D
      Copy the code
    2. Configuration webpack

      Note that this plugin is not written in plugins, but in optimization.minimizer, and the mode must be production

          const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
          module.exports = {
              mode:'production'.optimization: {minimizer: [new CssMinimizerPlugin(),
                  ]
              }
          }
      Copy the code

Tree Shaking

Tree Shaking is a way to optimize the size of a package by removing redundant code. It’s not a configuration option in Webpack, it’s a set of functions that work together. Based on ESModules modularity (that is, only the modular code of ESModules can make Tree Shaking work), this is enabled by default in production

For example, suppose I have an element.js file with the following code

    // element.js
    export const Button =() = >{
        return document.createElement('button')
        // Impossible to execute code
        console.log('end')}// Unreferenced code
    export const Link=() = >{
        return document.createElement('a')}Copy the code

Then import the module and use the methods in the module

    import {Button} from './element.js'
    const btn = Button();
    document.body.appendChild(btn);
Copy the code

Packaging in mode:’ Production ‘looks like this:

Obviously, the unused code is not packaged and optimized

How is Webpack implemented? Let’s implement it step by step without enabling production mode

  1. UsedExports: Exports only used members

        module.exports:{
            / /... Omit other options
            mode:'none'.optimization: {usedExports:true}}Copy the code

    If you use VSCode like me, you can see that Link is dark, which means it is not used. If you want to remove the Link code, you can use the minimize below

  2. Minimize: Compress and remove code that is not being used

        module.exports:{
            / /... Omit other options
            mode:'none'.optimization: {usedExports:true.minimize:true}}Copy the code

    After packaging, there is no Link code in the final code, as shown below:

  3. ConcatenateModules: Whenever possible combine each module into a function

    The normal effect of packaging is for each module code to be placed in a separate function. If many modules are introduced, there will be many functions, which will affect execution efficiency and the size of the packaged file. ConcatenateModules :true allows you to combine code from multiple modules into a single function, allowing you to test the effect on your own

  4. SideEffects: Specifies the side effect code

    Tree Shaking automatically removes code from modules that is not referenced, but this behavior can be problematic in some scenarios, such as the exten.global.js module code shown below

        // implement uppercase
        String.prototype.capitalize = function(){
            return this.split(/\s+/).map(function(item){
                return item[0].toUpperCase()+item.slice(1)
            }).join(' ')}Copy the code

    The code to use is as follows, and since the module does not have any exports, you just import to use the capitalize() method, which we call the side effect code

        import './extend.global.js'
        'hello boy'.capitalize(); // Hello Boy
    Copy the code

    By default, Webpack4 treats all code as side effect code, so it packs all code into the final result, which of course packs extra code into the file and makes it too big. By default, Tree Shaking is enabled for Webpack5. The Tree Shaking function automatically removes code that is not referenced. The code above is not exported or used, so Webpack5 does not package extend.global.js. The result is an error that cannot capitalize() method found. SideEffect is used to solve this problem. It is used in two steps and the code is as follows:

    1. optimization.sideEffectsSet totrue: Tells Webpack to identifypackage.jsonSide effect flags or rules in (default istrue, so this step can not be set.)
    2. package.jsonaddsideEffectsattribute, can be the following values:
      • True: Tells WebPack that all modules are side effects

        If set to true, extend.global.js above is packaged into the final result

      • False: Tell WebPack that all modules have no side effects

        If set to false, extend.global.js is not packaged into the final result and the code will report an error

      • Array: Manually specify side effect files

        Using true or false goes to two extremes and may not be suitable for real development scenarios. You can set an array to specify the side effects file as follows:

            {
                sideEffects:["*.css"."*.global.js"]}Copy the code

        After configuration, WebPack encounters CSS files or withglobal.jsThe files at the end are automatically packaged into the final result

Code Splitting

The resource modules in the project are packaged into different files according to the rules set by us. After code segmentation, the start-up cost of the application can be reduced and the response speed can be improved

1. Configure multiple entry and output multiple package files

    module.exports = {
        entry: {home:'./src/index.js'.login:'./src/home.js'
        },
        output: {// name: entry name
            filename:'[name].bundle.js'}}Copy the code

The code for the two entry files is as follows:

    // home.js
    import {formatDate} from './utils'
    import './css/home.css'
    console.log('index',formatDate())
Copy the code
     // login.js
    import {formatDate} from './utils'
    import _ from 'lodash'
    console.log('login',formatDate())

    const user = {username:'laoxie'.password:123456}
    const copyUser = _.cloneDeep(user)
    console.log(user == copyUser)
Copy the code

When packaged, you see that two entry files have been automatically introduced into the HTML file:

The output file size is fine, too. Login.js has large files due to the introduction of LoDash

But what if both home.js and login.js were introduced with LoDash? In the image below, we can see that the two js files are both large, indicating that WebPack has repackaged Lodash.

Solve the double packaging problem

Next, we use dependOn and splitChunks to solve the repackaging problem

  • Method 1: dependOn: By modifying the entry configuration, use dependOn to extract the public LoDash

        module.exports = {
            entry: {home: {import:'./src/index.js'.dependOn:'common'
                },
                login: {import:'./src/login.js'.dependOn:'common'
                },
                common:'lodash'}}Copy the code

    As a result, common.bundle.js (lodash code) has been extracted successfully, home.bundle.js and login.bundle.js are small, and the HTML file has successfully imported these three JS files

  • Method 2: splitChunks built into Webpack (recommended)

        module.exports = {
            / /... Other configuration
            entry: {home:'./src/index.js'.login:'./src/login.js'
            },
            optimization: {// Split the code
                splitChunks: {chunks:'all'}}}Copy the code

    Only need to configure optimization. SplitChunks. Chunks options, said what code needs to be optimized, the value for the following three:

    • Initial: initial block
    • Async: load blocks on demand (default)
    • All: all pieces

    We chose all optimization, and after packaging, the same three files will be output and normally introduced in the HTML file, as shown in the following figure

Note: Either way, if you are using a multi-page application, you need to configure the chunks option in the HTML-webpack-plugin plugin to prevent the repeated introduction of HTML files

   plugins:[
        new HtmlWebpackPlugin({
            chunks: ['common'.'home']}),new HtmlWebpackPlugin({
            filename:'login.html'.chunks: ['common'.'login']]}),Copy the code

2. ESModules Dynamic Imports

The import() module is dynamically imported using ECMAScript2019 (ES10), which returns a Promise object as follows

    import('lodash').then(({default: _}) = >{
        // Introduce the default attribute of the lodash module
    })
Copy the code

As long as you use LoDash in your code in this way, WebPack packs LoDash separately, as shown below:

Note: modules introduced dynamically with import() are compared to modules imported via import… from … Statically introduced modules are different in that modules are not directly written in HTML files, but introduced when the page is opened. Depending on the user’s actions (such as clicking and other operations), they can already achieve the deadload function

Also, as you can see, the generated public module file name is very long (it gets longer as more modules are created), and we can use webPack magic comments to solve this problem

     import(/*webpackChunkName:'common'*/'lodash').then(({default: _}) = >{})Copy the code

PS: Using Webpack magic annotation can also achieve the preloading function, just add webpackPrefetch:true,

  • Lazy loading: not loading when a page is entered, but loading when a module is used
  • Preload: load first when entering the page, using the direct call method

Webpack preloads the required modules only after the rest of the page has been loaded, so it does not affect the loading speed of the page

portal

  • Webpack 5 Tutorial 1: Getting to know Webpack
  • Webpack5 tutorial 2: resource modules and loaders
  • Webpack5 tutorial 3: plugins
  • Webpack5 tutorial 4: use the server to improve development efficiency
  • Webpack5 Tutorial 5: How to Optimize WebPack packaging