directory
  1. The world’s martial arts, only fast can not break
  2. New features
  3. Get in the car. Get ready for the upgrade
  4. Upgrade pothole avoidance guide
  5. Continue to accelerate
  6. Analysis of compilation results
  7. Tree -shaking, constrained coding
  8. At the end

For today’s front-end projects, compiling and publishing is almost a must, from lightning-fast compilations in a few seconds to snail’s pace compilations in 10 minutes or more. Especially when it comes to online hot fixes, every second counts and the response speed directly affects the user experience. Users won’t have the patience to wait so long for you to compile slowly. If the payment operation is involved, the product loss is measured in seconds, every one second ahead of the release, in front of Tencent’s massive users, can recover a large loss. In addition, the improvement of compilation efficiency brings the most direct benefit is the double improvement of development efficiency and development experience.

So what’s slowing down WebPack packaging, and what can we do to improve it?

Webpack is a very popular packaging tool at present. As of 6 days ago, WebPack 4 has been updated to version 4.28.3. In 10 months, the small version has been updated dozens of times, which shows the prosperity of the community.

When webpack4 was released, it was reported to be 60% to 98% faster.

The world’s martial arts, only fast can not break

Since the local project was upgraded to Webpack 4 a few months ago, webpack was manually downgraded to version 3.12.0 in order to obtain test data, and the rest of the configuration was left largely unchanged.

During the test, Mac only ran common IM, mailbox, terminal, browser, etc. In order to avoid the impact of plug-ins on data as much as possible, I closed some optimized plug-ins and reserved only common Loader and JS compression plug-ins.

The following are screenshots of five tests in [email protected] and [email protected] respectively.

Data analysis is as follows (unit: ms) :

The first The 2nd The third time 4 times The fifth time On average, Speed up
webpack3 58293 60971 57263 58993 60459 59195.8
webpack4 42346 40386 40138 40330 40323 40704.6 45%

Pure version upgrade, the compilation speed is increased to 45%. Here, I select mature online running projects, and the improvement of construction speed is meaningful only based on mature projects. Demo project is difficult to reflect the complexity of the construction environment due to the small compilation file base, and there may be large errors in testing. Meanwhile, the gap with the official data is mainly due to different projects and configurations.

Either way, the nearly 50% increase in compile speed is worth trying to upgrade webpack4! Of course, optimization has just begun, so read on.

New features

In order to upgrade webpack4 more smoothly, we need to understand it first.

Webpack4 introduces several new features while greatly improving compilation efficiency:

  1. / SRC/directory, default entry./ SRC /index.js, default output./dist directory, default output. The default output file is./dist/main.js.

  2. Out of the box with WebAssembly, WebPack4 provides WASM support and can now import and export any WebAssembly module, as well as write a loader to import C++, C, and Rust. (Note: WebAssembly modules can only be used in asynchronous chunks)

  3. Provide the mode attribute, set to Development for the best development experience, and set to Production for project compilation and deployment, such as enabling Scope and tree-shaking.

  4. The new plugin system provides new apis for plugins and hooks, with the following changes:

    • All hooks are managed by hooks objects, which treat all hooks as extendable class attributes
    • To add a plug-in, you need to provide a name
    • When developing a plug-in, you can choose the type of plug-in (sync/callback/promise)
    • Hooks = {myHook: new SyncHook(…); } to register the hook

    For more information on how plug-ins work, see how the New plug-in system works.

Get in the car. Get ready for the upgrade

First, the webpack-dev-server plug-in needs to be updated to the latest, and webpack-CLI is also required because webpack-CLI takes on the webpack4 command line related functions.

The mode attribute must be specified, otherwise convention takes precedence over configuration, and the production environment will be compiled by default.

WARNING in configuration The ‘mode’ option has not been set, Webpack will fallback to ‘production’ for this value. Set ‘mode’ option to ‘development’ or ‘production’ to enable Defaults for each environment. You can also set it to ‘none’ to disable any default behavior. Learn more: Webpack.js.org/concepts/mo…

There are two ways to add a mode configuration.

  • Specify — mode in package.json script:

    "scripts": {
        "dev": "webpack-dev-server --mode development --inline --progress --config build/webpack.dev.config.js"."build": "webpack --mode production --progress --config build/webpack.prod.config.js"
    }
    Copy the code
    • Add the mode attribute to the configuration file

      module.exports = {
        mode: 'production' / / or development
      };
      Copy the code

      After upgrading to webpack4, some of the default plug-ins have been replaced by the optimization configuration as follows:

      • CommonsChunkPlugin abandoned by optimization. SplitChunks and optimization. RuntimeChunk substitution, the former split code, which extract the runtime code. The original CommonsChunkPlugin produced modules with duplicate code and could not optimize asynchronous modules. The configuration of Minitchunks was also complicated. SplitChunks solved this problem. . In addition, the optimization runtimeChunk is set to true (or {name: “manifest”}), will have to pick up the entrance to the module of the runtime.
      • NoEmitOnErrorsPlugin abandoned by optimization noEmitOnErrors alternative, production environment opened by default.
      • NamedModulesPlugin abandoned by optimization namedModules alternative, production environment opened by default.
      • ModuleConcatenationPlugin abandoned by optimization concatenateModules alternative, production environment opened by default.
      • Optimize.UglifyJsPlugin obsolete, optimization. Minimize replacement, production environment default on.

      Not only that, Optimization provides the following default configuration:

      optimization: {
          minimize: env === 'production' ? true : false.// Development environment does not compress
          splitChunks: {
              chunks: "async".// Three values are available: initial(initial module), async(on-demand module), and all(all module)
              minSize: 30000.// Modules over 30K are automatically removed to public modules
              minChunks: 1.// If the module is referenced >= once, it is split
              maxAsyncRequests: 5.// The number of concurrent requests for asynchronously loading chunk <=5
              maxInitialRequests: 3.// The number of chunks loaded concurrently in an entry <=3
              name: true.// The default module name is hash. Multiple modules with the same name will be merged into one
              automaticNameDelimiter: '~'.// Name the delimiter
              cacheGroups: { // The cache group inherits and overwrites the splitChunks configuration
                  default: { // Module cache rule, set to false, default cache group will be disabled
                      minChunks: 2.// The modules are referenced >=2 times, and are then split into vendors public modules
                      priority: - 20./ / priority
                      reuseExistingChunk: true.// Use existing modules by default
                  },
                  vendors: {
                      test: /[\\/]node_modules[\\/]/.// splits modules in node_modules by default
                      priority: - 10}}}}Copy the code

        SplitChunks are important to unpack and optimize. If your project contains third-party components such as Element-UI (large components), it is recommended to unpack them separately, as shown below.

        splitChunks: {
            // ...
            cacheGroups: {    
                elementUI: {
                    name: "chunk-elementUI".// Unpack elementUI separately
                    priority: 15.// The weight must be greater than other cache groups
                    test: /[\/]node_modules[\/]element-ui[\/]/}}}Copy the code

          For more usage, see the comments above or the official documentation SplitChunksPlugin.

          Upgrade pothole avoidance guide

          Webpack 4 no longer supports Node 4. Due to the new JavaScript syntax, Tobias, one of the founders of Webpack, recommends that users use Node version >= 8.94 for optimal performance.

          There are all kinds of errors you can encounter after a formal upgrade, of which the following are common.

          VueLoaderPlugin needs to be added to the Webpack for VUE-Loader V15.

          const { VueLoaderPlugin } = require("vue-loader"); // const VueLoaderPlugin = require("vue-loader/lib/plugin"); // The two are equivalent
          
          / /...
          plugins: [
            new VueLoaderPlugin()
          ]
          Copy the code

            After upgrading to WebPack4, the mini-CSS-extract-Plugin replaces the extract-text-webpack-plugin as the preferred CSS packaging option, which has the following advantages:

            1. Asynchronous loading
            2. Better performance without recompiling
            3. Easier to use

            Bug that does not support CSS hot updates. Therefore, csS-hot-loader should be introduced in the development environment to support CSS hot update, as shown below:

            {
                test: /\.scss$/,
                use: [
                    ...(isDev ? ["css-hot-loader"."style-loader"] : [MiniCssExtractPlugin.loader]),
                    "css-loader",
                    postcss,
                    "sass-loader"]}Copy the code

              The CSS is optimized for compression before it is released to production, using the optimization-CSS-assets-webpack-plugin.

              const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
              
              / /...
              plugins: [
                  new OptimizeCssAssetsPlugin({
                      cssProcessor: cssnano,
                      cssProcessorOptions: {
                          discardComments: {
                              removeAll: true}}})]Copy the code

                Continue to accelerate

                At the beginning of this article, I mentioned that optimization is just beginning. Yes, as projects get more complex and Webpack gets slower, there must be a way to squeeze performance even further.

                After running and testing multiple projects over a long period of time, the following lessons have worked well.

                1. Reduce the compilation scope and reduce unnecessary compilation work. That is, modules, mainFields, noParse, includes, exclude, and alias are all used.

                  const resolve = dir => path.join(__dirname, '.. ', dir);
                  
                  // ...
                  resolve: {
                      modules: [ // Specify the following directories to find third-party modules to avoid Webpack's recursive search to the parent directory
                          resolve('src'),
                          resolve('node_modules'),
                          resolve(config.common.layoutPath)
                      ],
                      mainFields: ['main'].// Only use the main field as the entry file description field to reduce the search steps
                      alias: {
                          vue$: "vue/dist/vue.common"."@": resolve("src") // Cache SRC directories as @ symbols to avoid repeated addressing}},module: {
                      noParse: /jquery|lodash/.// Ignore files that are not modularized, so jquery or LoDash will not be parsed by loaders below
                      // noParse: function(content) {
                      // return /jquery|lodash/.test(content)
                      // },
                      rules: [
                          {
                              test: /\.js$/,
                              include: [ // Only the following directories are parsed, reducing the processing scope of the Loader
                                  resolve("src"),
                                  resolve(config.common.layoutPath)
                              ],
                              exclude: file => /test/.test(file), // Exclude the test directory file
                              loader: "happypack/loader? id=happy-babel" // More on that later]}},Copy the code
                  • If you want to further improve compilation speed, you need to know where the bottleneck is? Through the test, it is found that there are two slow stages: ① Babel loaders parsing stage; ② JS compression stage. Loader parsing will be discussed later, and JS compression is the last stage of publishing and compiling. Usually webPack needs to be stuck for a while. This is because js compression requires the code to be parsed into the AST syntax tree, then the AST needs to be analyzed and processed according to complex rules, and finally the AST is returned to JS. This process is computationally intensive and therefore time-consuming. As shown below, compilation looks stuck.

                    In fact, with the Webpack-parallel-Ugliffe -plugin, the process can be sped up twice as fast. We all know that Node is single-threaded, but node can fork a task. Based on this, webpack-parallel-uglip-plugin can split the task into multiple sub-processes for concurrent execution, and then send the result to the main process, thus implementing concurrent compilation. In this way, js compression speed is greatly improved. The configuration is as follows.

                    const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
                    
                    // ...
                    optimization: {
                        minimizer: [
                            new ParallelUglifyPlugin({ // Multi-process compression
                                cacheDir: '.cache/',
                                uglifyJS: {
                                    output: {
                                        comments: false,
                                        beautify: false
                                    },
                                    compress: {
                                        warnings: false,
                                        drop_console: true,
                                        collapse_vars: true,
                                        reduce_vars: true}}}),]}Copy the code

                      Of course, I tested five sets of data, and here are the screenshots:

                      Data analysis is as follows (unit: ms) :

                    The first The 2nd The third time 4 times The fifth time On average, Speed up
                    webpack3 58293 60971 57263 58993 60459 59195.8
                    Webpack3 comes with the ParallelUglifyPlugin 44380 39969 39694 39344 39295 40536.4 46%
                    webpack4 42346 40386 40138 40330 40323 40704.6
                    Webpack4 comes with ParallelUglifyPlugin 31134 29554 31883 29198 29072 30168.2 35%

                    With the webpack-parallel-Ugli-fi plugin, the build speed of WebPack3 can be increased by 46%. Even after upgrading to webpack4, the build speed increased by a further 35%.

                    1. Now let’s see how the loader parsing speed can be improved. HappyPack, like the webpack-parallel-uglip-plugin, can compile concurrently, which can greatly improve the speed of loader parsing.

                      const HappyPack = require('happypack');
                      const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
                      const createHappyPlugin = (id, loaders) => new HappyPack({
                          id: id,
                          loaders: loaders,
                          threadPool: happyThreadPool,
                          verbose: process.env.HAPPY_VERBOSE === '1' // make happy more verbose with HAPPY_VERBOSE=1
                      });
                      Copy the code

                        Loader: “happypack/loader? Id =happy-babel”, you need to create a happy-babel plug-in instance in your plugins.

                        plugins: [
                            createHappyPlugin('happy-babel', [{
                                loader: 'babel-loader',
                                options: {
                                    babelrc: true,
                                    cacheDirectory: true // Enable caching}}]]Copy the code

                          HappyPack starts 3 processes (default number of cpus -1).

                          In addition, vue-loader and CSS-loader support happyPack acceleration, as shown below.

                          plugins: [
                              createHappyPlugin('happy-css', ['css-loader', 'vue-style-loader']),
                              new HappyPack({
                                  loaders: [{
                                      path: 'vue-loader',
                                      query: {
                                          loaders: {
                                              scss: 'vue-style-loader!css-loader!postcss-loader!sass-loader?indentedSyntax'
                                          }
                                      }
                                  }]
                              })
                          ]
                          Copy the code

                            Based on WebPack4, equipped with Webpack-parallel-Ugli-fi – Plugin and happyPack plug-in, test screenshots are as follows:

                            Data analysis is as follows (unit: ms) :

                          The first The 2nd The third time 4 times The fifth time On average, Speed up
                          Only carry ParallelUglifyPlugin 31134 29554 31883 29198 29072 30168.2 35%
                          ParallelUglifyPlugin and happyPack 26036 25884 25645 25627 25794 25797.2 17%

                          It can be seen that the happyPack plugin can still improve the compilation speed by 17% on the basis of the webpack-parallel ugli-fi -plugin. In fact, because sass and other loaders do not support happyPack, HappyPack still has room for improvement. For more information, please refer to happypack.

                          1. As we all know, when webpack is packaged, some framework code is basically unchanged, such as Babel-Polyfill, vue, vuex, AXIos, Element-UI, Fastclick, etc. These modules also have a large size. Each compilation must be loaded once, which is time-consuming and laborious. These modules can be pre-packaged using the DLLPlugin and DLLReferencePlugin plug-ins.

                            To complete the DLL process, we need to prepare a new WebPack configuration, webpack.dll.config.js.

                            const webpack = require("webpack");
                            const path = require('path');
                            const CleanWebpackPlugin = require("clean-webpack-plugin");
                            const dllPath = path.resolve(__dirname, ".. /src/assets/dll"); // Directory for storing DLL files
                            
                            module.exports = {
                                entry: {
                                    // Put vUE related modules into a separate dynamic link library
                                    vue: ["babel-polyfill"."fastclick"."vue"."vue-router"."vuex"."axios"."element-ui"]
                                },
                                output: {
                                    filename: "[name]-[hash].dll.js"./ / generated vue. DLL. Js
                                    path: dllPath,
                                    library: "_dll_[name]"
                                },
                                plugins: [
                                    new CleanWebpackPlugin(["*.js"] and {// Clear the previous DLL file
                                        root: dllPath,
                                    }),
                                    new webpack.DllPlugin({
                                        name: "_dll_[name]".// manifest.json describes what the dynamic link library contains
                                        path: path.join(__dirname, ". /"."[name].dll.manifest.json")})]};Copy the code

                              Next, you need to add the DLL command to package.json.

                              "scripts": {
                                  "dll": "webpack --mode production --config build/webpack.dll.config.js"
                              }
                              Copy the code
                              • 1
                              • 2
                              • 3

                              After running NPM run DLLS, generates the. / SRC/assets/DLL/vue. DLL – [hash]. Public js and js. / build/vue. DLL. The manifest. Json resource description file, thus the DLL preparation is complete, Then reference it in wepack.

                              externals: {
                                  'vue': 'Vue'.'vue-router': 'VueRouter'.'vuex': 'vuex'.'elemenct-ui': 'ELEMENT'.'axios': 'axios'.'fastclick': 'FastClick'
                              },
                              plugins: [
                                  ...(config.common.needDll ? [
                                      new webpack.DllReferencePlugin({
                                          manifest: require("./vue.dll.manifest.json"})] : []]Copy the code

                                DLL public js does not change easily, and if an update does occur in the future, the new DLL file name will need to be added with the new hash, so that the browser does not cache the old file and cause execution errors. Because of the uncertainty of the hash, there is no way to specify a fixed link script in the HTML entry file, but the add-asset-html-webpack-plugin automatically imports DLL files.

                                const autoAddDllRes = () => {
                                    const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
                                    return new AddAssetHtmlPlugin([{ // Inject DLL js into HTML
                                        publicPath: config.common.publicPath + "dll/".// The path injected into the HTML
                                        outputPath: "dll".// Final output directory
                                        filepath: resolve("src/assets/dll/*.js"),
                                        includeSourcemap: false,
                                        typeOfAsset: "js" // options js, CSS; default js
                                    }]);
                                };
                                
                                // ...
                                plugins: [
                                    ...(config.common.needDll ? [autoAddDllRes()] : [])
                                ]
                                Copy the code

                                  With the DLL plug-in, the compilation speed of Webpack4 is further improved, as shown in the screenshot below:

                                  Data analysis is as follows (unit: ms) :

                                The first The 2nd The third time 4 times The fifth time On average, Speed up
                                ParallelUglifyPlugin and happyPack 26036 25884 25645 25627 25794 25797.2 17%
                                ParallelUglifyPlugin, happyPack, and DLL are available 20792 20963 20845 21675 21023 21059.6 22%

                                It can be seen that the compilation speed of Webpack4 can still be improved by 22% with DLL installed.

                                To sum up, we summarize the above multiple times of data and get the following table:

                                The first The 2nd The third time 4 times The fifth time On average, Speed up
                                webpack3 58293 60971 57263 58993 60459 59195.8
                                webpack4 42346 40386 40138 40330 40323 40704.6 45%
                                ParallelUglifyPlugin, happyPack, and DLL are available 20792 20963 20845 21675 21023 21059.6 181%

                                ParallelUglifyPlugin, happyPack, and DLL plugins can improve the compile speed by 181% and reduce the overall compile time by nearly two-thirds. And as the project progresses, this compilation improvement becomes more and more noticeable.

                                In fact, in order to obtain the above test data, I turned off the cache of Babel and ParallelUglifyPlugin. After enabling the cache, the average compile time of the second time was 12.8s, which is 362% faster than webPack3 due to the previous cache. Even if you’ve upgraded to webpack4, you’ll still see a 218% increase in compile speed with all three plugins listed above!

                                Analysis of compilation results

                                Of course, compile speed is an indicator that affects the developer experience more than the compiled file size. Webpack4 compiles files that are slightly smaller than the previous version. In order to better track file size changes, both development and production environments need to introduce the WebPack-bundle-Analyzer plug-in, as shown below.

                                The file size is as shown below:

                                Tree -shaking, constrained coding

                                sideEffects

                                Since webpack2, tree-shaking is used to eliminate useless modules, relying on the static structure of ES Modules, and turning on useless Module detection by setting “modules”: false in the.babelrc file, which is relatively rough. Webapck4 provides a flexible extension of garbage detection, mainly by setting sideEffects: false in package.json to tell the compiler that the project or module is pure and that garbage can be removed.

                                For tree-shaking to really work, when importing resources, it is important to import only the required components, as shown below:

                                import { Button, Input } from "element-ui"; // Import only required components
                                Copy the code

                                  At the end

                                  In the process of upgrading webpack4, stomping pits is a must, the key is stomping pits, what do you get?

                                  In addition, in addition to some optimization methods introduced in the article, more optimization strategies are being verified step by step…