preface
We already know how to use webpack. Now let's talk about how to improve its performance.
1, HMR (module hot load)
When we pack the file, we find that only modifying a small part of the content in one module will repackage the whole. Then if there are 10000 modules in our project, we only modify the content in one module. There is no need to repackage all of them. Just repackage this module, which is what we call hot loading.
- Style file: HMR function can be used: because style loader is implemented internally
- js file: HMR function cannot be used by default -- > js code needs to be modified and code supporting HMR function needs to be added
Note: the HMR function can only handle other files that are not imported js files. - html file: HMR function cannot be used by default At the same time, it will cause problems: html files cannot be hot updated (HMR function is not needed)
Solution: modify the entry entry and import the html file
Here, a property hot:true is added to configure devServer to realize the hot loading of css files.
devServer: { contentBase: resolve(__dirname, 'build'), compress: true, port: 3000, open: true, // Turn on HMR function // When the webpack configuration is modified, the webpack service must be restarted in order for the new configuration to take effect hot: true }
Modifying the entry file configuration can make the html file hot loaded, and add the html file path.
entry: ['./src/js/index.js', './src/index.html'],
Add and implement the hot loading of js in the entry file
// Once module If hot is true, HMR function is enabled. -- > Make HMR function code effective if (module.hot) { // Method will listen for changes in the a.js file. Once changes occur, other modules will not be repackaged. // The following callback function will be executed module.hot.accept('./a.js', function() { }); }
2, source_map
Source map: it is a technology that provides source code to post build code mapping (if the post build code is wrong, the source code error can be tracked through mapping).
Through webpack config. Add the following configuration implementation to the last position in the JS file
devtool: 'eval-source-map'
There are the following parameters
[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map
Parameter name | map file location | describe |
---|---|---|
source-map | external | Error code accurate information and error location of source code |
inline-source-map | inline | Only one inline source map error code is generated. The exact information and the error location of the source code |
hidden-source-map | external | The error code is the cause of the error, but there is no error location. The source code error cannot be tracked. Only the error location of the built code can be prompted |
eval-source-map | inline | Each file generates a corresponding source map, which is in eval, the accurate information of the error code and the error location of the source code |
nosources-source-map | external | The error code contains accurate information, but there is no source code information |
cheap-source-map | external | Error code accurate information and error location of source code can only be accurate lines |
cheap-module-source-map | external | For the exact information of the error code and the error location of the source code, the module will add the source map of the loader |
It's called inline and external
Inline: the generated map file is in build JS, build JS after each file
External: generate a map file separately
Note: inline builds faster than external
Examples
We will print JS is modified as follows
function print() { const content = 'hello print'; console.log(content)();//An error occurred here } export default print;
After running html, report an error directly on the console, as shown in the figure. Click the red marked position to directly jump to the error location in the source code.
Development environment configuration
Two aspects need to be considered in the development environment: fast speed and more friendly debugging
- Fast speed (EVAL > inline > heap >...)
Eval soap map is the fastest
Eval source map - More friendly debugging
souce-map
cheap-module-souce-map
cheap-souce-map
**It is suggested that both speed and debugging be more friendly: Eval source map / Eval soap module soap map (debugging is not as friendly as the former, but faster)**
Production environment configuration
Production environment needs to consider: should the source code be hidden? Should debugging be more friendly
- Inlining will make the code larger, so you don't need to inline in the production environment
- Nosources source map all hidden
- Hidden source map only hides the source code and will prompt the error message of the built code
Recommended: source map / soap module soap map
3, oneOf
When using webpack to package, each file will go through the configuration options in the rules in the configuration file to see if they are hit. This is unnecessary. Just match one. To achieve this effect, you only need to put the configured loader in the oneOf array, so that when a loader is hit, it will not continue to match the virtual loader.
Note: there cannot be two loaders in the oneOf array to process the same file. If so, you can extract the preferred loader to the outside of the oneOf array. As shown below
module: { rules: [ { // In package Eslintconfig -- > airbnb in JSON test: /\.js$/, exclude: /node_modules/, // Priority implementation enforce: 'pre', loader: 'eslint-loader', options: { fix: true } }, { // Only one of the following loader s will match // Note: two configurations cannot handle the same type of file oneOf: [ { test: /\.css$/, use: [...commonCssLoader] }, { test: /\.less$/, use: [...commonCssLoader, 'less-loader'] }, { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: {version: 3}, targets: { chrome: '60', firefox: '50' } } ] ] } }, { test: /\.(jpg|png|gif)/, loader: 'url-loader', options: { limit: 8 * 1024, name: '[hash:10].[ext]', outputPath: 'imgs', esModule: false } }, { test: /\.html$/, loader: 'html-loader' }, { exclude: /\.(js|css|less|html|jpg|png|gif)/, loader: 'file-loader', options: { outputPath: 'media' } } ] } ] }
4, Cache
The idea of caching is to cache the packaged files after the first packaging. If the files are modified, only the changed files will be modified. How to achieve it?
babel cache
You only need to add cacheDirectorv: true when using babel to process js
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: { version: 3 }, targets: { chrome: '60', firefox: '50' } } ] ], // Enable babel cache // The previous cache is not read until the second build cacheDirectory: true } }
File resource cache
Use hash value
Each time wepack is built, a unique hash value will be generated. The hash value can be used to judge whether the file is repackaged to update the cache.
Problem: because js and css use a hash value at the same time. If I repackage, I may only change one file, but it will invalidate all caches.
output: { // Add a hash value to the output file name filename: 'js/built.[contenthash:10].js', path: resolve(__dirname, 'build') }
chunkhash
Hash value generated according to chunk. If the package comes from the same chunk, the hash value is the same
Question: the hash values of js and css are still the same, because both css and js are in build js, so they belong to the same chunk. That is to say, js files and css files will be bound together. If css is modified, js will also update the cache.
output: { filename: 'js/built.[chunkhash:10].js', path: resolve(__dirname, 'build') }
contenthash
contenthash: generates a hash value according to the contents of the file. The hash value of different files must be different, so that the code can run online and the cache can be used better
output: { filename: 'js/built.[contenthash:10].js', path: resolve(__dirname, 'build') }
5, Tree shaking
Understanding: you can think of an application as a tree. The application will introduce a third-party library. However, our application does not use all the third-party library files. Those that are not used can be regarded as dead leaves in the book. Shake the tree to determine which are dead leaves and remove them, so as to reduce the volume of the application.
Premise of use:
- ES6 modularization must be used
- Open production environment
In package Configuration in JSON
"sideEffects": false All code has no side effects (can be done) tree shaking)
There may be problems with the above configuration: the css / @babel/polyfill (side effect) file may be killed. The following method can be avoided.
// Marked resources will not be processed //The following * represents any css file and less file "sideEffects": ["*.css", "*.less"]
6, Code segmentation
Understanding: under normal circumstances, when we use webpack for packaging, no matter how many js files there are in the source file, there is only one js file after the final packaging, and the split code will be packaged into multiple js files, at least realizing on-demand loading.
1. Multi entry file
entry: { // Multiple entries: there is one entry, and the final output has a bundle index: './src/js/index.js', test: './src/js/test.js' }, output: { // [name]: take the file name filename: 'js/[name].[contenthash:10].js', path: resolve(__dirname, 'build') }
The above configuration will output index hash. JS and test hash. JS two files.
2,optimization
- Node can be_ The code in modules packs a chunk separately for final output
- As long as it is multi entry, it will package multiple chunks, and automatically analyze whether there are public files in the multi entry chunk. If so, it will be packaged into a single chunk
optimization: { splitChunks: { chunks: 'all' } }
3. Control through js code
Through js code, a file can be packaged into a chunk separately. Import dynamic import syntax: a file can be packaged separately
import(/* webpackChunkName: 'test' */'./test') .then(({ mul, count }) => { // File loaded successfully~ // eslint-disable-next-line console.log(mul(2, 5)); }) .catch(() => { // eslint-disable-next-line console.log('File loading failed~'); });
Code segmentation summary
- By default, only one file is output for a single entry, and several files are output for multiple entries.
- optimization: if it is a single entry, only nodes will be split_ Modules file. If there are multiple entries, it will also analyze whether there are public files. If there are, it will be packaged into a file.
- Controlled by js code.
7, Lazy loading
Load only after triggering some conditions, rather than load immediately.
document.getElementById('btn').onclick = function() { // Lazy loading ~: the file is loaded only when it needs to be used. In this example, it is loaded only after clicking yes. import(/* webpackChunkName: 'test'*/'./test').then(({ mul }) => { console.log(mul(4, 5)); }); };
Normal loading: it can be considered as parallel loading (loading multiple files at the same time)
Preload prefetch: after other resources are loaded, the browser will be idle before use, and then load resources secretly. (poor compatibility)
document.getElementById('btn').onclick = function() { import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => { console.log(mul(4, 5)); }); };
8, PWA
Progressive network development application (accessible offline), using workbox -- > workbox webpack plugin
const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); new WorkboxWebpackPlugin.GenerateSW({ /* Two configurations: 1. Help serviceworker start quickly 2. Delete old serviceworker Generate a serviceworker configuration file~ */ clientsClaim: true, skipWaiting: true })
In index Handling compatibility problems in JS Worker Registration
if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker .register('/service-worker.js') .then(() => { console.log('sw Registration succeeded~'); }) .catch(() => { console.log('sw Registration failed~'); }); }); }
It should be noted that
- eslint doesn't know the global variables of window and navigator
Solution: you need to modify the package eslintConfig configuration in JSON
"eslintConfig": { "extends": "airbnb-base", "env": { "browser": true// Support browser side global variables } }
- sw code must run on the server
–> nodejs
Step 1: npm i serve -g
Step 2: serve -s build starts the server and exposes all resources in the build directory as static resources
9, Multi process packaging
First download: thread loader. You need to modify the Babel loader configuration
{ test: /\.js$/, exclude: /node_modules/, use: [ 'thread-loader', { loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: { version: 3 }, targets: { chrome: '60', firefox: '50' } } ] ] } } ] }
Starting multi process packaging has advantages and disadvantages, because the process startup is about 600ms and the process communication also has overhead. Only when the work takes a long time, multi process packaging is required.
Configuration with controllable number of processes
{ test: /\.js$/, exclude: /node_modules/, use: [ { loader: 'thread-loader', options: { workers: 2 // Open two processes to package } }, { loader: 'babel-loader', options: { presets: [ [ '@babel/preset-env', { useBuiltIns: 'usage', corejs: { version: 3 }, targets: { chrome: '60', firefox: '50' } } ] ] } } ] }
10, externals
Ignore packaging. Ignore the need to manually import the ignored files again after packaging
For example:
externals: { // Refuse jQuery to be packaged jquery: 'jQuery' }
CDN can be introduced
11, DLL
Function: indicates that the webpack libraries are not involved in packaging, and the dll will package some libraries separately to avoid repeated packaging, and package the node library into a window.
When you run webpack, you will find webpack by default config. JS configuration file, but we need to run webpack dll. JS file, then execute the following statement to run webpack
webpack --config webpack.dll.js
webpack.config.js
const { resolve } = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const webpack = require('webpack'); const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin'); module.exports = { entry: './src/index.js', output: { filename: 'built.js', path: resolve(__dirname, 'build') }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }), // Tell webpack which libraries are not involved in packaging, and the name of the libraries used will change~ new webpack.DllReferencePlugin({ manifest: resolve(__dirname, 'dll/manifest.json') }), // Package and output a file, and automatically import the resource into html new AddAssetHtmlWebpackPlugin({ filepath: resolve(__dirname, 'dll/jquery.js') }) ], mode: 'production' };
Using dll technology, some libraries (third-party libraries: jquery, react, vue...) are packaged separately. The following configuration is required
webpac.dll.js
const { resolve } = require('path'); const webpack = require('webpack'); module.exports = { entry: { // [name] -- > jQuery generated by final packaging // ['jquery '] -- > the library to be packaged is jQuery jquery: ['jquery'], }, output: { filename: '[name].js', path: resolve(__dirname, 'dll'), library: '[name]_[hash]' // What is the name of the content exposed in the packaged library }, plugins: [ // Package to generate a manifest JSON -- > provides and jquery mapping new webpack.DllPlugin({ name: '[name]_[hash]', // The exposed content name of the mapping library path: resolve(__dirname, 'dll/manifest.json') // Output file path }) ], mode: 'production' };
jquery is packaged separately and the mapping relationship file is generated
summary
webpack performance optimization has a lot of content and is a little complex, and you need to try to find the specific difference. Keep going