Can't you enjoy the core knowledge of webpack? Come and watch 10000 words of advanced knowledge

Posted by mothermugger on Sat, 15 Jan 2022 12:13:26 +0100

🤾‍♀️ preface

Previous article We talked about some basic features of webpack, but it's not enough to know the basic features alone. Therefore, in today's article, we will bring you the advanced features of webpack, including but not limited to the differential packaging of dev environment and prod environment, as well as the techniques of code segmentation of projects using webpack.

No more nonsense. Let's start today's study~ 🎳

🏓 1, Tree Shaking

1. Cited examples

Suppose we have a need to write a program to add and subtract two numbers. Now, let's implement this function. The specific codes are as follows:

export const add = (a, b) => {
    console.log(a + b);
}

export const minus = (a, b) => {
    console.log(a - b);
}

Next, we introduce it in the portal file. The specific codes are as follows:

import { add } from './math.js';

add(1, 2);

In this state, we use the npx webpack command to package the project. To view the packaged file code:

/***/ "./src/math.js":
/*!*********************!*\
  !*** ./src/math.js ***!
  \*********************/
/*! exports provided: add, minus */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "add", function() { return add; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "minus", function() { return minus; });
const add = (a, b) => {
  console.log(a + b);
};
const minus = (a, b) => {
  console.log(a - b);
};

/***/ })

We can find that we only introduce the addition operation in the entry file, because we only want to use the addition function at present, and we don't need to use subtraction for the time being. However, the contents of the subtraction part are also packaged in the packaged file. This invisible, more or less added a lot of trouble to us.

Therefore, we need to introduce Tree Shaking in webpack to solve this problem.

2. Tree Shaking configuration

First of all, we need to be in webpack common. JS. If it is in the Development mode, it does not own Tree Shaking by default. Therefore, the following configuration is required. The specific code is as follows:

//Core module of node
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');

module.exports = {
	mode:'development',
	optimization: {
		usedExports: true
	}
}

In the development environment, we need to add the optimization module to start Tree Shaking for the project.

Next, let's continue with package JSON. Under this file, we need to add the following code:

{
	"sideEffects": false
}

Sideeffects: what does false mean? When set to false, it indicates that the Tree Shaking operation is enabled for all es modules.

It is worth noting that Tree Shakig only supports the introduction of ES Module, not commonJS. This is because the underlying implementation of ES Module is static, while the implementation of commonJS is dynamic.

Another situation is that if you want to make some modules not turn on Tree Shaking, you can configure sideEffects as follows. The specific codes are as follows:

{
	"sideEffects": [
		"*.css",
		"@babel/poly-fill" 
	]
}

The above code means that the tree shaking function is not enabled for all css files and @ Babel / poly fill.

Next, let's look at the effect in the development environment after the configuration is completed. The specific codes are as follows:

/***/ "./src/math.js":
/*!*********************!*\
  !*** ./src/math.js ***!
  \*********************/
/*! exports provided: add, minus */
/*! exports used: add */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return add; });
/* unused harmony export minus */
const add = (a, b) => {
  console.log(a + b);
};
const minus = (a, b) => {
  console.log(a - b);
};

/***/ })

/******/ });
//# sourceMappingURL=main.js.map

You can see that in the development mode, the subtraction function still exists, just one more sentence / *! exports used: add * / to indicate that only add is used.

Why? In the development mode, webpack is afraid that if other modules are introduced in this place, deletion will easily lead to an error, so it does not delete them.

However, if the mode is in the production environment, the role of Tree Shaking is more obvious. After modifying the mode, let's look at the packaged results. The specific codes are as follows:

function (e, n, r) { "use strict"; r.r(n); var t, o; t = 1, o = 2, console.log(t + o) }

In the production environment, there is only one line after packaging, and Xiaobian extracts the final part.

As you can see, when in the production environment, the packaged results only show the addition function. We didn't use subtraction, so we won't package it together at this time.

By the way, let's talk about the difference between development and Production modes of webpack.

🏸 2, Differentiation and packaging of Development and prediction modes

1. Project packaging structure

Usually, our project will have three webpack configuration files. One is webpack common. JS, one is webpack Dev.js, and the other is webpack.js prod.js . The first file is used to store the common configuration in the development environment and production environment, the second file is used to store the configuration in the development environment, and the third file is used to store the configuration in the production environment.

Next, let's look at the code of these three configuration files.

2. There is a total configuration of webpack common. js

If we don't write the common file, the code coincidence between dev and prod will be high, so we extract the same part. The specific codes are as follows:

//node core module
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
    // Place the entry file and specify how to package it
	entry:{
		main: './src/index.js'
	},
    module:{
		rules:[{
			test: /\.m?js$/,
			//exclude, as the name suggests, is excluded. If the js file is in node_ Under the modules folder, we will be excluded
			// Because node_module is usually from a third-party library. It has automatically handled this part of the work, so we don't need to repeat it
			exclude: /node_modules/,
			use: {
			  loader: "babel-loader",
			}
		  },{
			test:/\.(jpg|png|gif)$/,
			use:{
				loader:'file-loader',
				options: {
					//placeholder
					name: '[name]_[hash].[ext]',
					outputPath: 'images/',
					limit: 10240
				}
			}
		},{
			test:/\.scss$/,
			use:[
				'style-loader', 
				{
					loader: 'css-loader',
					options: {
						//It indicates that sass loader and postcss loader should be taken first
						importLoaders: 2,
						modules: true
					}
				}, 
				'sass-loader',
				'postcss-loader'
			]
		},{
			test:/\.css$/,
			use:[
				'style-loader',
				'css-loader',
				'postcss-loader'
			]
		},{
			test: /\.(eot|ttf|svg)$/,
			use: {
				loader: 'file-loader',
			}
		}]
	},
    plugins: [new HtmlWebpackPlugin({
		//Indicates which template to reference
		template: 'src/index.html'
	}),new CleanWebpackPlugin(['dist'])
	],
    // Output, indicating how the webpack should be output
	output: {
		publicPath: '/',
		//Multiple files can be generated with []
		filename: '[name].js',
		// It refers to the file under which the packaged file should be placed
		path: path.resolve(__dirname, 'dist')
	}
}

3. Development environment: webpack dev.js

After pulling away the common code, now let's write webpack Dev.js file. The specific codes are as follows:

const webpack = require('webpack');
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');

const devConfig = {
	mode:'production',
	devtool: 'cheap-module-eval-source-map',
	devServer: {
		contentBase: './dist',
		// When npm run start is finished, it will automatically open the browser for us
		open: true,
		port: 8080,
		// Let's turn on the function of hotModuleReplacement in our webpackDevServer
		hot: true,
		// Even if the HMR does not take effect, the browser will not refresh automatically
		hotOnly: true
	},
	plugins: [
        //Hot module update
		new webpack.HotModuleReplacementPlugin()
	],
	optimization: {
		usedExports: true
	}
}

module.exports = merge(commonConfig, devConfig)

4. Production environment: webpack prod.js

Continue, let's write the detached prod code. The specific codes are as follows:

const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');

const prodConfig = {
	mode:'development',
	devtool: 'cheap-module-source-map'
}

module.exports = merge(commonConfig, prodConfig)

Here, by comparing the codes of the above three files, we can find that such code separation makes our project structure clearer.

5. Run the project package json

After the above configuration is completed, let's now think about whether the running commands should also be distinguished if we want to run the configuration that is not used in the development environment and the production environment. Therefore, we are interested in package The JSON file makes the following configuration:

{
    "scripts": {
    	"dev": "webpack-dev-server --config webpack.dev.js",
    	"build": "webpack --config webpack.prod.js"
  }
}

Through the above configuration, we can run the project through the commands npm run dev and npm run build to distinguish whether the project is a development environment or a production environment.

At the same time, if we want to see the error message more intuitively on the console, in the development environment, we can run without webpack dev server and directly with webpack. The specific codes are as follows:

{
    "scripts": {
    	"dev-server": "webpack --config webpac.dev.js",
    	"dev": "webpack-dev-server --config webpack.dev.js",
    	"build": "webpack --config webpack.prod.js"
  }
}

In this way, we can use webpack to run the project through NPM run dev build.

⚽ 3, Webpack and Code Splitting, SplitChunksPlugin

1. Code Splitting

Sometimes, we may encounter a business logic with tens of thousands of lines of code. After packaging, all the tens of thousands of lines of code will be thrown into main JS file. Such a large file will make the loading speed of the whole project very slow. Therefore, we need to use Code Splitting to solve this problem.

We're on webpack common. JS. The specific codes are as follows:

module.exports = {
	optimization: {
		splitChunks: {
			chunks: 'all'
		}
	}
}

From the above code, we can know that the effect of code segmentation is achieved by using splitChunks in optimization.

What does webpack want to do after using this configuration?

In fact, after using splitChunks, when webpack encounters a public class library, it will automatically package and generate a new file for us, and then split the rest of the business logic into another file.

It is worth noting that whether the public class library is loaded synchronously or asynchronously, webpack can help us split the code.

2. Citing examples - SplitChunksPlugin

We talked about the code segmentation of webpack above. In fact, the underlying implementation principle of the code segmentation of webpack uses the plug-in splitChunksPlugin. Next, let's talk about this plug-in.

Before using SplitChunksPlugin, if we introduce a library asynchronously, the file name packaged by webpack will be named 0 js , 1.js and.

We now hope that during code segmentation, webpack can give custom names to our third-party libraries. What should we do?

3. Implementation method - SplitChunksPlugin

First, we add the webpackChunkName configuration to the imported library. The specific codes are as follows:

function getComponent() {
    return import(/*webpackChunkName:"lodash"*/'lodash').then(({ default: _ }) => {
        var element = document.createElement('div');
        element.innerHTML = _.join(['Monday', 'Tuesday'], '_');
        return element;
    })
}

getComponent().then(element => {
    document.body.appendChild(element);
})

/*webpackChunkName:"lodash" * / this sentence means that when we asynchronously introduce lodash into the library and want to split the code, that is, when we package the webpack, we name it lodash.

The first step of the above configuration knowledge is to install and use a dynamically introduced plug-in. The specific codes are as follows:

Install plug-ins:

npm install --save-dev @babel/plugin-syntax-dynamic-import

Yes Introduced under babelrc:

{
	// Plugins: ["dynamic import webpack"] unofficial Support Plug-Ins
	plugins: ["@babel/plugin-syntax-dynamic-import"]
}

Configure webpack common. js :

module.exports = {
	optimization: {
		splitChunks: {
			chunks: 'all',
			cacheGroups: {
				vendors: false,
				default: false
			}
		}
	},
}

4. SplitChunksPlugin configuration parameters

Next, let's look at some common configurations of SplitChunksPlugin. The specific codes are as follows:

module.exports = {
	optimization: {
		splitChunks: {
		  /*When async is used, only asynchronous code is split;
		  When all is used, the synchronous and asynchronous codes are split at the same time;
		  When initial is used, it means code splitting of synchronization code*/
		  chunks: 'all',
          //Code segmentation shall be performed when it is greater than 30kb
		  minSize: 30000,
          //Indicates the maximum size of the file after code splitting. If it exceeds, splitting will continue; If some files cannot be split, this configuration is basically useless
		  maxSize: 0,
		  minRemainingSize: 0,
          //At least two blocks are used before they can be extracted
		  minChunks: 2,
          //Indicates the number of modules loaded at the same time, up to 5
          /*For example, if we introduce 10 class libraries, we will do code segmentation 10 times.
          At this time, if we fill this parameter as 5, webpack will generate 5 js files for us from the first 10 libraries during packaging,
          After that, there will be no code segmentation, and all of them will be thrown into one file*/
		  maxAsyncRequests: 5,
          //The code segmentation of the entry file can only be divided into three js files at most. If there are more than three, the code segmentation will not be done again
		  maxInitialRequests: 3,
          //Intermediate symbol during file generation
		  automaticNameDelimiter: '~',
          //Make the file names in defaultVendors and default valid
		  name: true,
		  enforceSizeThreshold: 50000,
          //When packaging synchronization code, cacheGroups
		  cacheGroups: {
			defaultVendors: {
			  //Check whether the library you imported is on the node_module
			  test: /[\\/]node_modules[\\/]/,
			  priority: -10,
			  //Make sure it's on node_ After modules, package it and name it vendors js
			  filename: 'vendors.js'
			},
            //Code segmentation for non third-party library code
			default: {
				priority: -20,
				reuseExistingChunk: true,
                filename: 'common.js'
			}
		  },
		}
	}
}

🏐 4, Packaging analysis, Preloading, Prefetching

1. Packaging analysis

Packaging analysis means that when we use webpack to package code, we can use some tools of packaging analysis to analyze the files generated by our packaging, and then see whether the packaging is reasonable. So how to package analysis?

We need to use a third-party warehouse of github, Stamp this link to enter~

After understanding the contents of the document library, we'll start with the package JSON. The specific codes are as follows:

{
    "scripts": {
        "dev-build": "webpack --profile --json > stats.json --config ./build/webpack.dev.js",
        "dev": "webpack-dev-server --config ./build/webpack.dev.js",
        "build": "webpack --config ./build/webpack.prod.js"
  	}
}

Through the above code, we can analyze: add configuration -- profile -- json > stats before -- config json, which means that the package analysis file will be generated after the webpack is packaged. This file is called stats json, meanwhile, - json means stats The format of json file is a json format.

After generating stats After the JSON file, we can put it into the packaging tool for analysis. You can locate it in the official documents bundle-analysis , which provides webpack chart, webpack visualizer and other visual tools for us to use. You can select the corresponding tools according to your personal needs JSON file.

2. Preloading,Prefetching

(1) Cited examples

In fact, when we configure splitChunks, the default value of chunks is async. That is, if we do not configure it, webpack will only code split asynchronous code by default. Why does webpack do this?

webpack thinks it's good to package the synchronous code in one file. At the same time, it hopes that we should write more asynchronous loading code, so as to really improve the performance of our website.

Now, let's talk about a very common scene in life. Suppose we are logging in to Zhihu website now, we haven't logged in when we first went in. What we hope now is that by clicking the login button, the corresponding modal box of login will be displayed, rather than waiting for it to load after clicking, which can make the page loading speed much faster.

What should I do? This brings us to preloading and prefetching in webpack.

(2) preloading and prefetching

Suppose we now introduce a click JS file, then we can handle it like this. The specific codes are as follows:

document.addEventListener('click', () => {
    // When the main js files are loaded, and then the network bandwidth is free, it will secretly load them/ click.js file to load
    import(/* webpackPrefetch: true */ './click.js').then(({default: func}) => {
        func();
    })
});

Through the above code, we can know that in front of the introduced file, i.e/ click.js, add / * webpackPrefetch: true * / to achieve the desired effect. This sentence means that when the main js files are loaded, that is, when the network bandwidth is free, then the webpack will sneak in this time period/ click.js file to load.

Prefetch is mentioned here, but in fact, we can also change webackprefetch to webackpreload. The difference between preload and prefetch is that preload is loaded at the same time as the main file, not after the main file is loaded. Generally speaking, we use prefetch. Only when the main file is finished, can we load the remaining files we want. Such logic and page optimization are perfect.

To sum up, when we package libraries such as jQuery and lodash, we only need to load them during the first access. When we access them for the second time, we can improve the access speed with the help of cache. But this method only improves the speed of the second visit, and what we want to achieve is that when we visit for the first time, webpack can make the page load fastest.

So we finally use preload and prefetch to solve this problem.

🏏 5, Code segmentation of CSS files

1. css file code segmentation

Above, we talked about how to split the code of js files. Now, let's talk about how to split the code of css files. For the code segmentation of css files, we will refer to Official documents A plug-in mentioned in: MiniCssExtractPlugin. Next, let's take a look at how to use this plug-in.

Step 1: install the plug-in. The specific commands are as follows:

npm install --save-dev mini-css-extract-plugin

Step 2: use in development environment and online environment. Open us common. JS file, introduce the plug-in and use it. The specific codes are as follows:

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
	entry:{
		main: './src/index.js'
	},
    module:{
		rules:[{
			test:/\.scss$/,
			use:[
				MiniCssExtractPlugin.loader, 
				{
					loader: 'css-loader',
					options: {
						//It indicates that sass loader and postcss loader should be taken first
						importLoaders: 2,
						modules: true
					}
				}, 
				'sass-loader',
				'postcss-loader'
			]
		},{
			test:/\.css$/,
			use:[
				MiniCssExtractPlugin.loader,
				'css-loader',
				'postcss-loader'
			]
		}]
	},
    plugins: [
		new MiniCssExtractPlugin({
			// If the file is directly referenced, go to filename
			filename: '[name].css',
			// If the file is indirectly referenced, go to chunkFilename
			chunkFilename: '[name].chunk.js'
		})
	],
	optimization: {
		//Using treeShaking
		usedExports: true,
		splitChunks: {
			chunks: 'all',
			cacheGroups: {
				vendors: false,
				default: false
			}
		}
	  }
}

Step 3: configure package JSON file. The specific codes are as follows:

{
  "sideEffects": [
    //Indicates that treeShaking is not enabled for css files
    "*.css"
  ]
}

2. Compress css files

For the packaged css file, its size is still relatively large, so we need to compress the file size. What should I do?

**Step 1: * * install the plug-in. The specific commands are as follows:

npm install optimize-css-assets-webpack-plugin -D

Step 2: use in development environment and online environment. We open webpack common. JS file, introduce the plug-in and use it. The specific codes are as follows:

const CssMinimizerPlugin = require("optimize-css-assets-webpack-plugin");

module.exports = {
    optimization: {
        minimizer: [new OptimizeCSSAssetsPlugin({})]
    }
}

3. Merge and package css files

Sometimes, we may have many entry files, and each entry file has its corresponding css files. So at this time, we want to package all css files into the same file. What should we do?

We need to be on webpack common. JS. The specific code is as follows:

module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        styles: {
          name: "styles",
          test: /\.css$/,
          chunks: "all",
          enforce: true,
        },
      },
    },
  }
}

From the above code, we can see that we need to configure an additional cacheGroups of styles in splitChunks to package all css files into a folder named styles.

🏑 6, webpack and browser caching

1. Browser cache configuration

When we first visit the website, we always need to load various files from scratch for the first load. Assuming that our code is not updated, we hope to pull the cache directly from the browser instead of reloading. Wait until our code is updated, and then reload the web page. What should I do?

Step 1: configure the development environment webpack Dev.js file. The specific codes are as follows:

const devConfig = {
    output: {
		filename: '[name].js',
		chunkFilename: '[name].chunk.js',
	}
}

Step 2: configure the production environment webpack Prod.js file. The specific codes are as follows:

const prodConfig = {
    output: {
		filename: '[name].[contenthash].js',
		chunkFilename: '[name].[contenthash].js',
	}
}

The above code aims to solve the problem of adding a hash value to the output file when the environment is online. At this time, if our code changes, webpack will generate a new hash value and the web page will be updated. If our code does not change, the hash value will remain the same, and the web page will pull the memory information from the browser and load it.

2. Solve the problem of old version

The above content cannot take effect if it occurs in some relatively low webpack versions. Therefore, we need to make a configuration to be compatible with the low version problem. We're on webpack common. JS. The specific code is as follows:

module.exports = {
    optimazation: {
       	runtimeChunk: {
        	name: 'runtime'
    	} 
    }
}

⚾ 7, Shimming's role

1. Shimming gasket

Go on, now let's take a look at the concept of shimming, or gasket, in webpack.

In the packaging process of webpack, we often have to do some code compatibility or packaging process compatibility.

For example, for two js files, modules are independent of each other, and there is no coupling between them. Suppose we now have two files, and the specific code is as follows:

jquery.ui.js:

export function ui(){
    $('body').css('background', 'red');
}

index.js:

import _ from 'lodash';
import $ from 'jquery';

import { ui } from './jquery.ui';

ui();

const dom = $('<div>');
dom.html(_.join(['Mondady', 'Tuesday']), '~');
$('body').append(dom);

As you can see, now we want to be in index ui. JS file, but in this file, it does not introduce the jquery library. Therefore, if you run directly like this, it is certain that an error will be reported.

Therefore, if we want to solve this problem, how to configure it?

Next, we will discuss webpack common. JS. The specific codes are as follows:

const webpack = require('webpack');

module.exports = {
    plugins: [
        new webpack.ProvidePlugin({
            $: 'jquery',
            //'_': 'lodash',
            //_join: ['lodash', 'join']
        })
    ]
}

Therefore, this can be understood indirectly as taking something to pad the $and we have the concept of shimming in the title.

2. this point

For files in the project, the point of this is to the module itself, not to the global. What should we do if we want all js files in the project to point to the global?

Step 1: install the loader. The specific commands are as follows:

npm install imports-loader --save-dev

Step 2: configure webpack common. js . The specific codes are as follows:

module.exports = {
    module: {
        rules: [{
			test: /\.m?js$/,
			exclude: /node_modules/,
			use: [{
				loader: "babel-loader",
			},{
				loader: 'imports-loader?this=>window'
			}]
		  }]
    }
}

After configuring the imports loader, webpack indicates that it will point all this points in the js file to the global window.

🎖️ 8, Conclusion

In today's article, we learned how to use Tree Shaking to optimize the packaging size of code. At the same time, we learned how to distinguish packaging in Dev and Prod environments. We need to clarify the same configuration and different configurations in these two modes and the relationship between them.

In addition, we also learned to use webpack to split the code of js files and css files. And use webpack to package, analyze and preload the code in advance.

Finally, we also learned about how webpack turns on browser caching and the role of Shimming.

That's all for the basic and advanced features of webpack! I hope it will help you~

If you have any questions or the article is wrong, please leave a message in the comment area 💬 Or add vx: MondayLaboratory communication~ 😉

🐣 One More Thing

(: recommended in previous periods)

(: Fan Wai Pian)

  • Pay attention to the official account of Monday's research room. First, we will focus on quality articles.

  • If this article is useful to you, remember to leave a footprint jio before you go~

  • The above is the whole content of this article! See you next time! 👋👋👋

Topics: Javascript Front-end Webpack postcss