Project information

Webpack build optimization must be combined with the project. The company has a large multi-page front-end project. It will take a long time to get that project off the ground. About ten minutes. This is a living sample, so let’s optimize the build process step by step.

Build slowly, optimize. But how to optimize. In fact, how to optimize is not the most important thing, the most important thing is to understand why the build is slow, and what are the reasons for the slow build. To understand why the build is slow, you must analyze the output. There are two kinds of analysis tools, one is to analyze packaging volume and the other is to analyze packaging time.

Code volume analysis

npm i webpack-bundle-analyzer
Copy the code

Method of use

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [new BundleAnalyzerPlugin()]
}
Copy the code

First look at the analysis of the construction results

  • The overall chunk size is 79.63 M
  • The largest chunk is 34.94 M

It should be noted that @h3/report, @h3/ ANTD-vue, @H3Print, echarts, @h3-icons, vxe-table/lib these chunks are component libraries introduced in the project. It’s incredibly big. It’s incredibly big. The real source code SRC, form-detail, form-Modal, common adds up to 18 meters.

Handling of external component libraries

From the analysis results, the @H3 / Report component is too large. Look at @h3/report in node_modules, and the main option in the package.json file of the @h3/report component indicates the entry file.

  "main": "./dist/report.js"
Copy the code

However, the dist/report.js file is only 2.77 MB in size, which is far from the real 28.94 MB. As you can see from the resulting diagram, the report introduces the lib directory, not the dist directory as expected. Note that there is a problem with the referenced file. Sure enough, the following configuration was found in the vue.config.js file first in the babel.config.js file

plugins: [
    ['import', { libraryName: '@h3/report', libraryDirectory: 'lib', style: false }, '@h3/report'],
]
Copy the code

The second is in the vue.config.js file

transpileDependencies: [
	'@h3/report'
]
Copy the code

You can see that the previous generation dug a hole to introduce the @h3/report/lib directory in the raw source code.

By default, babel-loader ignores all files in node_modules. If you want to explicitly translate a dependency through Babel, you can list it in this option.

Clearly @h3/ Report is a third party component and should not be involved in the package build of the current project. You should refer to the @h3/report packaged code built under dist. After deleting the above configuration, repackage and build the package. The result is as follows: The overall size is reduced from 79.63 M to 34.17 M. Fifty percent optimization. Even so, there are still many problems with the optimized report. Since report relies on echart, Mxgraph and other components, the volume of the package is increased by 5 ~ 6 M. The @h3/ Report component was developed by other project teams. Further optimization requires cross-departmental communication and coordination.

Extract common code

The reference project is a large multi-page application with a unified technology stack: VUE. In addition, helper methods and generic components are common and placed under the common directory. Component libraries are also common. If the common code is not separated, then the same component can be repeatedly punched into multiple chunks. Modules in node_modules are common to all pages, business-neutral, and can be removed. In addition, multiple single pages in the project share the common directory, which can also be extracted.

config.optimization.splitChunks({
	cacheGroups: {
		vendors: {
			name: 'vendors',
			test: /[\\\/]node_modules[\\\/].+(\.js)$/,
			priority: -5,
			chunks: 'initial',
			reuseExistingChunk: true,
			minChunks: 2,
		},
		common: {
			name: 'common',
			priority: 5,
			chunks: 'initial',
			reuseExistingChunk: true,
			minChunks: 2,
		},
	},
});
Copy the code

Build time analysis

npm install speed-measure-webpack-plugin --save-dev
Copy the code

If it is based on Webpack

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin"); const smp = new SpeedMeasurePlugin(); module.exports = smp.wrap({ ...... // webpack configuration});Copy the code

If it is based on vuE-CLI

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin"); const smp = new SpeedMeasurePlugin(); module.exports = { configureWebpack: smp.wrap({ ...... // webpack configuration})}Copy the code

The time information is divided into two parts: the plugins time and the loaders time.

Parallel compression

You can see that UglifyJsPlugin consumes 2 min and 50 SEC. It takes so long, which is why at the end of the build phase (90%), we find that we are stuck, and the build schedule doesn’t change. Near the end of the build, Babel transcoding is complete, and at this point, the code compression phase is handled, which is a CPU-intensive task. Webpack uses the UglifyJS plug-in for compression, since javascript is single-threaded, but modern cpus are multi-core. A single thread can only be used on one kernel, while the rest of the kernel is idle, which is completely wasteful. You can use the ParallelUglifyPlugin plug-in. The principle is to decompose a large compression task into several small compression tasks, and each small compression task is handed to a thread for processing, and finally the results are summarized.

New ParallelUglifyPlugin({uglifyJS: {output: {beautify: false, // remove all comments: false,},}}),Copy the code

Repackage build time reduced by 1 min. The ParallelUglifyPlugin can be used to exploit idle computing power.

Parallel transcoding

It can be seen that vue-loader takes about 1 min and 50 SEC. This is based on the VUE project and there are a lot of VUE files. The ts-loader takes about 1 minute and 40 SEC. The less-loader takes about 1 minute and 40 SEC. Babel-loader takes about 1 min and 20 SEC, etc. Code analysis and transformation requires a large number of read and write operations, which are computationally intensive tasks and time-consuming when the project is large. Javascript is single-threaded, and the Webpack that runs on it is single-threaded. Therefore, Webpack processes tasks one by one, not all at once. Thread-loader enables Webpack to handle multiple tasks at the same time, exerting the power of a multi-core CPU computer.

npm install --save-dev thread-loader
Copy the code

Don’t use Happypack for pull requests that are out of repair.

rules: [{
		test: /\.js$/,
		loaders: ["thread-loader", "babel-loader"],
		include: [path.join(__dirname, "src"), path.join(__dirname, "modules")],
	}, {
		test: /\.ts$/,
		loaders: ["thread-loader", {
			loader: "ts-loader",
			options: {
              happyPackMode: true
            }
		}],
		include: [path.join(__dirname, "src"), path.join(__dirname, "modules")],
	},
	{
		test: /\.(css|less)$/,
		loaders: ["thread-loader", "css-loader", "less-loader", "postcss-loader"],
		include: [path.join(__dirname, "src"), path.join(__dirname, "modules")],
	},
	{
		test: /\.vue$/,
		loaders: ["thread-loader", "vue-loader"],
		include: [path.join(__dirname, "src"), path.join(__dirname, "modules")],
	},
]
Copy the code

After reconstruction, the total time of the optimized effect was shortened from 5 min 13 SEC to 3 min 8 SEC. Parallel execution of loaders shortens transcoding time by 2 min.

Narrow the file search

Use include and exclude to avoid loader processing unnecessary files. Take the JS file as an example.

Only files in the SRC and modules directories are processed. The depracted directory of obsolete history files is not dealt with.

	{
		test: /\.js$/,
		loaders: ["thread-loader", "babel-loader"],
		include: [path.join(__dirname, "src"), path.join(__dirname, "modules")],
	},
Copy the code

Specify webpack to look for third-party modules in node_modules in the project root directory

Resolve. Modules defaults to [‘node_modules’], which means to look for the desired module in the current directory./node_modules. /node_modules, then go to.. /.. /node_modules, and so on. This is similar to node.js’ module finding mechanism.

If the installed third-party modules are stored in the./node_modules directory of the project root directory, there is no need to search layer by layer in the default way. You can specify the absolute path to save the third-party modules to reduce the search.

Module.exports = {resolve: {// exports = {resolve: {// exports = {resolve: {// exports = {resolve: { [path.resolve(__dirname, 'node_modules')] }, };Copy the code

Optimize the resolve.alias configuration

Third party modules such as Vue are used in the project. Components referenced in a project using import Vue from ‘Vue’ are files specified in the module or main fields of Vue’s package.json.

  "main": "dist/vue.runtime.common.js",
  "module": "dist/vue.runtime.esm.js",
Copy the code

You can see that the VUE used is a runtime version, lacks a compiler, and is small. The online environment is suitable for using the runtime environment, and the local development environment is suitable for using the full version of VUE. Often there are multiple versions of a set of code, using alias to point to the compressed min version.

Optimize the resolve.extensions configuration

When an import statement does not have a file suffix, Webpack automatically suffixes it and tries to ask if the file exists. The resolve.extensions are used to configure the list of suffixes to use during the attempt. The default is:

extensions: ['.js', '.json']
Copy the code

Most of the files in this project are VUE files, a few are TS files and few js files. File suffixes with the highest frequency should be placed first in order to exit the search process as quickly as possible.

resolve: {
	extensions: ['.vue', '.ts', '.js']
}
Copy the code

Optimize the module.noparse configuration

Some libraries, such as jquery, are large and complete on their own without adopting modular standards, making it time-consuming and pointless for Webpack to parse these files.

Note that omitted files should not contain modular statements such as import, require, define, etc. This will result in code that contains modular statements that cannot be executed in the browser environment.

module: {
    noParse: [
        /vue\.common\.prod\.js$/,
        /moment\.js$/,
        /h3-grid\.js$/,
        /jquery/,
        /lodash/,
	]
}
Copy the code