preface

I think the configuration of Webpack is a black hole for many front-end, first of all, there are too many configurations, and then the optimization of the project is always unable to start. Later, I concluded that there are such points of pre-compilation, caching, multi-threading and less retrieval after looking at the configuration of packaging performance optimization.

Before we talk about performance optimization, we need to know how to analyze our package performance.

Performance analysis tool

Performance analysis is mainly divided into package size analysis and package time analysis.

Package size analysis

The packaged size analysis plugin is Webpack-bundle-Analyzer, which visualizes the size of our packaged modules.

First we write a command in package.json:

// package.json
"scripts": {
    "analyzer": "npm run build && webpack-bundle-analyzer --port 8888 ./dist/analyzer.json"
}
Copy the code

Webpack. Prod. Js code:

// webpack.prod.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; .plugins:{
    ...
    new BundleAnalyzerPlugin({
      analyzerMode: 'disabled'.generateStatsFile: process.env.Analyzer === 'on'.statsFilename: path.join(__dirname, '.. / '.'dist/analyzer.json')})}Copy the code

The effect is as follows:

Of course, after we get the details of the size of our project modules, we can optimize to print out larger modules, such as LoDash. We don’t need to type in all methods, we can just extract the methods used.

Packaging time analysis

Of course, in webpack packaging, we need to know the size of all the packaged modules, and also analyze the time spent in each packaging link, so as to optimize the time-consuming operation of different links. For this, we need to download the speed-Measure-webpack-plugin.

In fact, the configuration is very simple, just need to be in our WebPack configuration package layer.

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");

const smp = new SpeedMeasurePlugin();

module.exports = smp.wrap(webpackConfig) // webpackConfig refers to packaging configuration
Copy the code

The time consumed in each link is as follows:

multithreading

HappyPack

Because a build requires parsing and processing of a large number of files, it is a file read and write and computationally intensive operation. As files grow, WebPack builds slowly because WebPack is a single-threaded model running on Node.js, so WebPack builds only one task at a time, not multiple tasks at a time.

There are two ways for Webpack to support multithreading: HappyPack(no maintenance) and Thread-loader.

HappyPack configuration:

const HappyPack = require('happypack');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });

module.exports = {
    rules: [{test: /\.jsx? $/,
        exclude: /node_modules/,
        loader: 'happypack/loader? id=jsx'},].plugins: [
      new HappyPack({
        id: 'jsx'.threadPool: happyThreadPool,
        loaders: [{loader: 'babel-loader'.options: {
              cacheDirectory: '.webpack_cache'}}]})]}Copy the code

When using babel-loader, the specified directory will be used to cache the execution result of loader. Subsequent WebPack builds will attempt to read the cache to avoid the potentially high-performance Babel recompilation process that can occur each time it is executed.

Of course, not all loaders need to use happypack to enable multithreading, because multithreading itself also takes time to start, loader can choose not to enable the project is not large or not time-consuming.

thread-loader

Thread-loader configuration: Thread-loader configuration is simpler than happypack.

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

The cache

cache-loader

Because every webpack compilation repacks all the files, it also means that many files will be recompiled without modification. In fact, this will lead to an increase in build time. In the performance overhead loader, you can use cache-loader to cache the results.

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

Babel-loader is equipped with a cache function, but some loaders do not have this function.

The base module is removed

webpack-dll-pllugin

The stripping of the base module is essentially the stripping of third-party dependencies that do not change very often. For example, react family bucket and Lodash are commonly used in the project.

In fact, we have to compile these third party dependent libraries every time we pack, which hardly needs to be changed, which causes us to waste a lot of time. We can use the Webpack-DLL-plugin library to pre-pack these basic modules into dynamic link libraries (a link library can contain multiple modules) in a pre-compiled way. There is no need to compile the base libraries each time they are packaged. As long as the versions of these third-party dependent libraries have not changed, we do not need to compile them again.

externals

In addition to using the webpack-DLL-plugin to precompile the base libraries, you can also import these libraries using CDN and optimize the build speed by not packaging them with the externals configuration of Webpack.

index.html:

<script
  src="https://code.jquery.com/jquery-3.1.0.js"
  crossorigin="anonymous"
></script>
Copy the code

webpack.config.js:

module.exports = {
    / /...
    externals: {
        'jquery': 'jQuery'}}Copy the code

Do this by importing $from ‘jquery’; You can still use jquery.

Narrow the file search

resolve.modules

Since Webpack searches for third party dependencies, it searches for./node_modules, and then goes up one level if it doesn’t. /node_modules. Therefore, we can specify a good path of third-party dependent libraries to reduce the search time.

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

resolve.extensions

When we import files that don’t have suffixes, we can reduce the number of searches by specifying resolve.extensions to tell Webpack the order in which suffixes should be searched, usually with the most frequent ones first.

module.exports = {
    / /...
    resolve: {
        extensions: ['jsx'.'js'.'json']}}Copy the code

module.noParse

Since some libraries, such as jquery and ChartJS, do not implement modular standards, parsing them would be time-consuming and pointless.

module.exports = {
    / /...
    module: {
        noParse: /jquery/}}Copy the code

Exclude and include of the loader

When compiling modules in webpack’s loader, we can specify exclude and include attributes. Exclude indicates which folders do not need to be compiled, and include indicates which folders do need to be compiled. Both use absolute paths.

const path = require('path');
module.exports = {
    / /...
    module: {
        rules: [{test: /\.js[x]? $/,
                use: ['babel-loader'].exclude: [path.resolve(__dirname, 'node_modules']}]},}Copy the code

Reference:

Easy to understand Webpack