In previous articles, we looked at the basic uses and principles of WebPack:

  • Webpack from shallow to deep Series (1)
  • Webpack from shallow to deep Series (2)
  • Webpack output file analysis
  • Package an on-demand vUE component library with Webpack
  • Thoroughly understand webpack principle: handwritten Webpack core code

As the project becomes more and more complex, it will face the problems of slow build speed and large file size. Webapck build optimization is something that must be considered for large projects, so let’s discuss the strategy of build optimization in terms of speed and volume.

Analysis tools

Before optimization, we need to understand some quantitative analysis tools and use them to help us analyze the points that need to be optimized.

webpackbar

Webpackbar displays real-time packaging progress during packaging. The configuration is also simple, adding to the plugins array:

const WebpackBar = require('webpackbar')
module.exports = {
  plugins: [
    ...
    new WebpackBar()
  ]
}
Copy the code

speed-measure-webpack-plugin

The speed-measure-webpack-plugin command is used to view the time consumption of each loader and plugin.

Slightly different from normal plug-in use, you need to wrap the entire WebPack configuration item with its wrap method.

const SpeedMeasurePlugin = require('speed-measure-webpack-plugin') const smp = new SpeedMeasurePlugin() module.exports =  smp.wrap({ entry: './src/main.js', ... })Copy the code

After packaging, the command line output shows which loaders and plugins take a long time and then optimizes them.

webpack-bundle-analyzer

Webpack-bundle-analyzer gives you a visual view of what modules are contained in a packaged bundle and the size of each module. Based on this information, we can analyze the project structure, adjust the packaging configuration and optimize it.

Add the plug-in to the plugins array. After the build is complete, the analysis results are displayed at http://127.0.0.1:8888/ by default.

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports = {
  plugins: [
    ...
    new BundleAnalyzerPlugin()
  ]
}
Copy the code

webpack-bundle-analyzerCalculates the size of the module file in three cases:

  • Stat: The original size of the file without any conversion
  • Parsed: the output size of a file after conversion (e.g. Babel-loader ES6->ES5, UglifyJsPlugin compression, etc.)
  • Gzip: size of a parsed file compressed by gzip

The use of speed-measure-webpack-plugin and Webpack-bundle-Analyzer itself also increases the packaging time (webpack-bundle-Analyzer is particularly time-consuming), so it is recommended that these two plug-ins be used for development analysis. In the production environment.

Optimize construction speed

Multi-process build

Webpack running on Node.js is single-threaded, and even if multiple tasks exist at the same time, they can only be executed one by one in a queue. When the project is more complex, the build is slower. Most cpus today are multicore, and there are tools that can take full advantage of the CPU’s multicore concurrency.

Common examples are Happypack and Thread-loader.

happypack

Happypack can split build tasks into multiple child processes for concurrent execution, and then send the results to the main process. HappyPack transfers the loader configuration to happyPack.

const Happypack = require('happypack') module.exports = { module:{ rules:[ { test: /\.js$/, use: },}, plugins:[new happypack ({id: Use: ['babel-loader'] // Loader})]}Copy the code

thread-loader

The authors of Happypack no longer have this project to maintain. After Webpack4, thread-loader can be used.

Thread-loader is simple to use by placing it in front of other loaders, as shown below. Loaders placed after this thread-loader will run in a separate worker pool.

module.exports = {
  module:{
    rules:[
      {
          test: /\.js$/,
          use: ['thread-loader','babel-loader']
      }
    ]
  },
}
Copy the code

For small projects, it is not recommended to start a multi-process build, as it takes time to start the process and the build will slow down.

Using the cache

Caching can be used to speed up secondary builds (the comparisons below are for secondary builds). When caching is used, there is a. Cache directory in node_modules that holds the contents of the cache.

cache-loader

Add this cache-loader before some performance expensive loaders to cache the results to disk.

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['cache-loader','babel-loader']
      }
    ]
  }
}
Copy the code

It can be seen that the build speed is significantly improved by using cache-loader.

babel-loaderUse caching, or notcache-loaderDirectly inbabel-loaderFollowed by? cacheDirectory=true

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: ['babel-loader?cacheDirectory=true']
      }
    ]
  }
}
Copy the code

hard-source-webpack-plugin

Hard-source-webpack-plugin enables caching of modules.

const HardSourceWebpackPlugin = require("hard-source-webpack-plugin")
module.exports = {
  plugins:[
    new HardSourceWebpackPlugin()
  ]
}
Copy the code

With the hard-source-webpack-plugin, the secondary build speed is approximately 90% faster.

include/exclude

Typically, the Loader processes all files that match the matching rules. Babel-loader, for example, iterates through all js files used in a project, compiling and converting the code for each file. Node_modules js files are almost always translated and do not need to be processed again, so we use include/exclude to help us avoid unnecessary translation.

Module. exports = {module:{rules:[{test: /\.js$/, use: ['babel-loader'], exclude: // node_modules/ // include: [path.resolve(__dirname, 'src')] } ] }, }Copy the code

Include specifies the search folder directly, which is more efficient and faster than exclude.

Dynamically linked library

The above babel-loader can avoid dealing with third-party libraries in node_modules by include/exclude.

However, if you pack both the third-party library code and the business code into a bundle, the plugins that handle the bundle, such as Uglifyjs-webpack-plugin and Terser-webpack-plugin, cannot do without the third-party library content.

In fact, the third party library code is basically mature, do not use what processing. Therefore, we can isolate the third-party library code for the project.

There are three common handling methods:

  1. Externals
  2. SplitChunks
  3. DllPlugin

Externals can avoid dealing with third-party libraries, but each of these libraries has to be imported by adding a script tag to the HTML document. Too many JS files downloaded on a page can affect the performance of the page, and sometimes we only use a small part of the functionality of the third-party library.

SplitChunks rebuild third-party libraries with each build, which does not speed up the build.

The DllPlugin and DLLReferencePlugin (used in conjunction), which are built-in plug-ins for WebPack, are recommended. The DllPlugin packages infrequently updated third-party libraries separately, and when the version of these third-party libraries does not change, there is no need to rebuild.

Usage:

  1. Package third-party libraries using DllPlugin
  2. Use the DLLReferencePlugin to reference manifest.json to disassociate the packages already wrapped in step 1
  • First, create a new WebPack configuration filewebpack.dll.jsUsed to package third-party libraries (Step 1)
const path = require('path') const webpack = require('webpack') module.exports = { mode: 'production', entry: { three: ['three', 'dat. GUI '] // Third party library array}, output: {filename: '[name].dll. Js ', //[name] is in entry path: path.resolve(__dirname, 'dist/lib'), library: '[name]' }, plugins: [ new webpack.DllPlugin({ name: '[name]', path: Path. resolve(__dirname, 'dist/lib/[name]. Json ') //manifest.jsonCopy the code

Once packed, you can see in thedistA lib folder has been added to the directory.

  • And then, we were inwebpack.base.jsMake a change to disassociate the package already packed in step 1 (Step 2)
Module. Exports = {plugins: [/ / modify CleanWebpackPlugin configuration new CleanWebpackPlugin ({cleanOnceBeforeBuildPatterns: ['! Lib /**' // Each time you clear the dist directory, Don't clean the lib folder content]}), / / DLL related configuration new webpack DllReferencePlugin ({/ / will be configured to manifest fields we step 1 in a json file package out of the manifest: require('./dist/lib/three.json') }) ] }Copy the code

When repackaged, you can see that the size of the whole project is reduced by 90% compared to the original 9.1MB, because it is a multi-page package (multi-page package configuration), and each page references the bulky three.js core file. When we removed the largest three.js core file from the bundle on each page, the bundle’s size was significantly reduced.

Look at the build time: 30% less than before using the DllPlugin.

Not only third-party libraries, but also the base libraries in business code can be separated by DllPlugin.

Optimized build volume

The code segment

Separating third-party libraries from the base libraries in the business code prevents a single bundle.js from becoming too bulky and taking too long to load. And in multi-page builds, it also reduces repeated packaging.

Common operations are through SplitChunks (described in detail in a previous article: SplitChunks) and dynamic link libraries (shown above), which are not covered here.

Dynamic import

The dynamic import function mainly reduces the volume of resources on the first screen. Non-first screen resources are requested when needed, which improves the loading speed of the first screen. A common example is route management for single-page applications (such as vue-router)

{ path: '/list', name: 'List', component: () => import('.. /views/List.vue') },Copy the code

Not directly import components (import List from ‘.. /views/ list.vue ‘), which will pack all the components into the same bundle. Instead, import components dynamically. Modules referenced by import() are packaged into separate bundles and loaded when needed. Dynamic import is recommended for resources with complex functions that are not required for the first screen.

<span @click="loadModal">show popup </span> /*** methods: {loadModal(){import('.. /modal/index.js') } } ***/Copy the code

treeShaking

Use ES6’s import/export syntax and import and export your code as follows instead of using Export Default.

Export const a = 1 export const b = 2 export function afunc(){} export {a, b, Import {a,b} from './util.js' console.log(a,b)Copy the code

In the Mode: Production environment, tree-shaking is automatically turned on, unused code is removed, and the afunc functions in the above example are not packaged into the bundle.

Code compression

Uglifyjs-webpack-plugin and terser-webpack-plugin are commonly used to compress JS code.

In webpack4, code compression is turned on by default in production. We can also configure our own to override the default configuration for more customized needs.

Before v4.26.0, the built-in compression plugin for WebPack was Uglifyjs-webpack-plugin. Starting with v4.26.0, this plugin was replaced by Terser-webpack-plugin. We also use the Terser-webpack-plugin as an example to configure the compression plugin in optimization.minimizer

const TerserPlugin = require('terser-webpack-plugin'); Module. exports = {optimization: {minimizer: [new TerserPlugin({parallel: true, // enable parallel compression, can speed up the build True, // If the production environment uses source-maps, it must be set to true})]}}Copy the code

Sprite figure

Sprite image splices several small ICONS into a large image. Sprite image can reduce HTTP requests and speed up web page display in http1.x environment.

The size of the Sprite icon should be small. Large images are not recommended to be spliced into Sprite images. It should also be a static icon on the site, not an icon retrieved dynamically through an Ajax request. Therefore, it is usually used as a website logo, icon and other pictures.

During development, the UI can provide Sprite images, but every time a new icon is added, it has to be re-created, recalculating the offset, which is more troublesome. Using webPack plug-in to synthesize Sprite images, you can directly use a single small icon during development, automatically synthesize Sprite images during packaging, and automatically modify the value of backbackground position in CSS.

Next, we use PostCSS-Sprites to automatically compose Sprite images.

First, configure postCSs-loader in webpack.base.js:

//webpack.base.js module.exports = { module: { rules: [ { test: /\.css$/, use: ['vue style-loader','css-loader', 'postCSs-loader '] // Configure postCSs-loader}, {test: /\. Less $/, use: ['vue style-loader',' CSs-loader ',' postCSs-loader ',' less-loader'] // Configure postCSs-loader}]}};Copy the code

Then create.postcsrc. Js in the project root directory and configure postCSs-sprites.

module.exports = { "plugins": [require('postcss-sprites')({// all static images used in CSS will be merged by default // Use filterBy to specify images to be merged, such as here, where only images under icon folder filterBy will be merged: function (image) { if (image.url.indexOf('/images/icon/') > -1) { return Promise.resolve(); } return Promise.reject(); } }})]Copy the code

The default is to merge the image into a Sprite image called sprite.png.

Specify the small icon as the background directly in CSS:

.star{ display: inline-block; height: 100px; width: 100px; &.l1{ background: url('.. /icon/star.png') no-repeat; } &.l2{ background: url('.. /icon/star2.png') no-repeat; } &.l3{ background: url('.. /icon/star3.png') no-repeat; }}Copy the code

After packaging, you can see that background-image and background-position are automatically modified.

gzip

The principle of Gzip, refer to explore the secret of Gzip compression in HTTP transport

Enable gzip compression to reduce file size. With a browser that supports GZIP, you can speed up resource loading. Gzip compression can be done by both the server and the client, when the server responds to a request and when the client application is built. However, the compression process itself is time consuming and CPU resources, if there is a large number of compression requirements, will increase the burden of the server.

So you can generate a gzip compressed file at build packaging time, put it on the server as a static resource, and return the compressed file directly upon receiving the request.

Using webpack to generate gzip files requires the use of compression-webpack-plugin, using the following configuration:

const CompressionWebpackPlugin = require("compression-webpack-plugin") module.exports = { plugins: [new CompressionWebpackPlugin ({test: / \. (js) | CSS $/, / / matching algorithm to compress the file: "gzip"})]}Copy the code

After the package is complete, in addition to the package file, additional compressed files with the suffix. Gz are generated. As you can see, the size of a gzip compressed file is much smaller than the size of an uncompressed file.

Project address: github.com/alasolala/w…