Webpack performance optimization
- How do I analyze performance data
- Optimization of compile time
- Optimization of compilation volume
- How to run faster
This article starts with a summary of the interaction between the electron main thread and the render thread from the previous article
How do I analyze performance data
It mainly introduces the use of some common tools
Install friendly-errors-webpack-plugin, node-notifier,speed-measure- webPack5-plugin, and webpack-bundle-Analyzer
$ yarn add friendly-errors-webpack-plugin speed-measure-webpack5-plugin webpack-bundle-analyzer -D
$ yarn add node-notifier
Copy the code
Friendly-errors-webpack-plugin: Identifies certain categories of Webpack errors and provides a better development experience
Node-notifier: supports multiple platforms. Since electron can also call Node-notifier, it is not installed as a development dependency
Speed-measure -webpack5-plugin: the speed-measure-webpack5-plugin is used in Webpackage 4 to analyze the packaging speed
Webpack-bundle-analyzer: Monitors the volume of packages. It needs to be used with webpack-CLI
Modify the WebPack configuration
render-process/config/webpack.common.js
const webpack = require('webpack') const path = require('path'); const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin') const notifier = require('node-notifier') Const icon = path.join(__dirname,' error.png ') module.exports = {entry: {// defaults to main main: './render-process/main/indexx' }, / / directory context, the context root directory https://webpack.docschina.org/configuration/entry-context/#context: process. The CWD (), the output: { filename: "[name].[hash].js", path: path.join(__dirname, ".. /dist-main"),}, target: "electron-renderer", resolve: { [" ts "and" benchmark ", "js", "JSX", "json"), alias: {/ / "@" : path. Resolve (" SRC "), / / configuration @ can point to the SRC directory},}, the module: { rules: [{ test: /\\.tsx?\$/, loader: "ts-loader", exclude: /node_modules/, }], }, plugins: [ // https://www.npmjs.com/package/friendly-errors-webpack-plugin new FriendlyErrorsWebpackPlugin({ onErrors:(severity, errors)=>{ console.log(errors,'errorserrorserrorserrorserrors') const error = errors[0] notifier.notify({ Title: 'webpack compilation fails, the message: severity + ":" + error. The name, the subtitle: error. File | |', icon,})}})],}Copy the code
/render-process/main/indexx’, NPM run build:react
Packaging time analysis, introduce SpeedMeasureWebpack5Plugin
render-process/config/webpack.common.js
const SpeedMeasureWebpack5Plugin = require('speed-measure-webpack5-plugin') const smp = new SpeedMeasureWebpack5Plugin() Module. Exports =smp.wrap({entry: {// default is main main: './render-process/main/index'}, //... })Copy the code
Let’s look at the information provided by SMP
DONE Compiled successfully in 4800ms 12:43:38pm SMP ⏱ >> General output time took 4.81 secs SMP ⏱ Each plugin available Plugins > > FriendlyErrorsWebpackPlugin took 0.011 secs SMP ⏱ Loaders > > loader when ts - loader took 2.077 secs Module count = 1 >> Time modules with no loaders took 0.23 secs module count = 9 >> Special extra work Html-webpack-plugin took 0.012 secs module count = 1Copy the code
Package file module size analysis, introducing Webpack-bundle-Analyzer
render-process/config/webpack.common.js
const SpeedMeasureWebpack5Plugin = require('speed-measure-webpack5-plugin') const smp = new SpeedMeasureWebpack5Plugin() const {BundleAnalyzerPlugin} =require('webpack-bundle-analyzer') module.exports =smp.wrap({ // ... plugins: [ new FriendlyErrorsWebpackPlugin({ // ... }), // https://www.npmjs.com/package/webpack-bundle-analyzer new BundleAnalyzerPlugin(), ], })Copy the code
/package.json adds an NPM command,–progress to indicate the monitoring process
"build:reactWithProgress": "webpack --progress --config render-process/config/webpack.prod.js"
Executing this command opens http://127.0.0.1:8888/ in the default browser
Save the packaged report locally
New BundleAnalyzerPlugin({analyzerMode:'disable', // do not start the web server to display the packaged report generateStatsFile:true // generate the report file})Copy the code
Json file (render-process/dist-main/stats.json)
Use the Web to view the local JSON package report file
Package. json adds the command to specify the address of the JSON file
"webpackAnalyzer": "webpack-bundle-analyzer --port 8888 ./render-process/dist-main/stats.json"
Copy the code
Optimization of compile time
Webpack.docschina.org/guides/buil…
There are two main ideas:
- Reduce the number of files to be processed
- Narrow down what you’re looking for
1. resolve
You can specify extensions instead of requiring or importing a file extension
Resolve: {/ / default suffix, introduced one by one to find, sorting strategy: file more put in front of, for example. Ts extensions: [", "benchmark" ts ", "js", "JSX", "json"],},Copy the code
2. alias
Configuring aliases speeds up WebPack’s search for modules
The bootstrap, for example, suppose that we should introduce the bootstrap. CSS, normally to import (‘ the bootstrap/dist/CSS/bootstrap CSS ‘), Since the main of the bootstrap package is dist/js/bootstrap.js, import(‘bootstrap’) cannot be used.
The bootstrap package. The json
"style": "dist/css/bootstrap.css",
"sass": "scss/bootstrap.scss",
"main": "dist/js/bootstrap.js",
Copy the code
Configure aliases to solve this problem and speed up the module’s search
resolve: {
alias: {
bootstrap:path.resolve(__dirname,'node_modules/bootstrap/dist/css/bootstrap.css')
}
},
Copy the code
3. MainFields and mainFiles
When the module finds the file, it refers to the main field of package.json of the corresponding package. If we don’t want to use main, we can use mainFields
Resolve: {// target==web or target==webworker,mainFields default: ['browser','module','main'] ['module','main'] mainFields:['style], // If package.json does not have those fields, use index file mainFiles:[index]},Copy the code
4. modules
When node_modules can’t be found in the current directory, Webpack looks for the parent directory, and specifying the modules path directly speeds up the search
resolve: {
modules:[
'c:/node_modules'
]
},
Copy the code
5.oneOf
OneO means that only one loader in the array can be matched. Once one loader is found, no other loader will be matched
module: {
rules: [{
oneOf:[
{
test: /\\.tsx?\$/,
loader: "ts-loader",
exclude: /node_modules/,
}
]
}],
}
Copy the code
6. externals
If we want to reference a library and we don’t want webpack to be packaged without affecting our use in development, we can use externals
The React example
externals: {
'react': 'React',
'react-dom': 'ReactDOM'
}
Copy the code
JQuery example
Jquery :' jquery'} // Assign window. jquery to \$import \$from 'jquery'Copy the code
HTML to add the CDN
CDN_LIST field is configured in HtmlWebpackPlugin, and the use of different CDN addresses in development environment and production environment or ejS template engine syntax in index.html is controlled by functionCopy the code
7. resolveLoader
ResolveLoader: configure the loader’s search directory, default node_modules, can extend the search scope (local loader function)
resolveLoader:{
modules:[path.resolve(__dirname,'xxx'),'node_modules']
}
Copy the code
8. noParse
The module.noParse field allows you to configure which module files do not need to be parsed, improving the overall build speed
Example: Files matching title.js will not be parsed when configured, and syntax such as import require cannot be used in title.js
module: {
noParse:/title.js/,
rules: [{
oneOf:[
{
test: /\\.tsx?\$/,
loader: "ts-loader",
exclude: /node_modules/,
}
]
}],
}
Copy the code
9.IgnorePlugin
Example: Use IgnorePlugin to ignore localized content
Install the moment YARN add moment
Refer to my moment
import moment from 'moment'
console.log(moment)
Copy the code
Comment out the configuration items and open the Package Analysis Web page
New BundleAnalyzerPlugin({// analyzerMode:'disable', // do not start the web server to display the packaged report // generateStatsFile:true, // Generate the report file}),Copy the code
When we look at the packaging report, we find that the local of moment occupies most of the volume of moment
Use IgnorePlugin for optimization
plugins: [
// https://www.npmjs.com/package/friendly-errors-webpack-plugin
new FriendlyErrorsWebpackPlugin({
// ...
}),
// https://www.npmjs.com/package/webpack-bundle-analyzer
new BundleAnalyzerPlugin({
// ...
}),
// https://webpack.docschina.org/plugins/ignore-plugin/
new webpack.IgnorePlugin({
resourceRegExp: /^\\.\\/locale\$/,
contextRegExp: /moment\$/,
})
],
Copy the code
It’s much smaller when you remove the language pack
Manually reference the language pack
index.tsx
import moment from 'moment'
import 'moment/locale/zh-cn'
console.log(moment)
Copy the code
10. thread-loader
It is not recommended unless the resources are very large and large
Install yarn add thread-loader -d
My side of the configuration on the packaging error, this will not elaborate
11. Leverage caching
Babel-loader uses the cache. You can try to use the cache when repacking to improve the packaging speed. The default location is node_modules/
use:[
{
loader:'babel-loader',
options:{
cacheDirectory:true
}
}
]
Copy the code
cache-loader
The installation
yarn add cache-loader -D
Configure cache-loader. In order to see the effect, I will import bootstrap and Lodash
Module: {rules: [{oneOf: [{test: /\\.tsx?\$/, // exclude node_modules. Exclude takes precedence over include. /node_modules/, include: path.resolve(__dirname, '../'), use: [ "cache-loader", "ts-loader" ], }, { test: /\\.css\$/, use:[ 'cache-loader', 'style-loader', 'css-loader' ] } ] } ], },Copy the code
When there is no cache, it takes 10 seconds
I used 8 seconds when I had a cache
By comparison of time, the packaging time changed from 10S to 8S. It can be seen that the change is not great. Maybe my project is too small
hard-source-webpack-plugin
Webpack5 has module caching built in, so you don’t need to use this plugin issues6527
Optimization of compilation volume
Optimize – CSS -assets-webpack-plugin: optimize and compress CSS
Terser-webpack-plugin: Optimize and compress JS
Image-webpack-loader: Image compression and optimization
The installation
yarn add optimize-css-assets-webpack-plugin terser-webpack-plugin file-loader image-webpack-loader -D
Js compressed
/ / optimizations https://webpack.docschina.org/configuration/optimization/#root optimization: {/ / open, the default is true to minimize: true, minimizer: [new TerserPlugin({ exclude:/nodu_modules/ })], },Copy the code
Turn off Optimization,main package volume
Turn on Optimization,main package volume
CSS compression
plugins:[
new OptimizeCssAssetsWebpackPlugin()
]
Copy the code
- Clearing useless CSS
Purgecss-webpack-plugin: Clears unnecessary CSS
Mini-css-extract-plugin: used with purgecss-webpack-plugin, extract CSS glob separately: used to find files
The MiniCssExtractPlugin may conflict with smp.wrap, so I will remove the SMP
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const PurgeCssWebpackPlugin =require('purgecss-webpack-plugin') const glob = require('glob') ... module: { rules: [ { oneOf: [ { // ... }, { test: /\\.css\$/, use: / / / 'cache - loader, / / MiniCssExtractPlugin. Replace' style - loader loader, MiniCssExtractPlugin. Loader, 'css-loader' ] } ] } ], }, plugins: [ new FriendlyErrorsWebpackPlugin({ // ... } }), new MiniCssExtractPlugin( { filename:'[name].css', } ), New PurgeCssWebpackPlugin ({/ / to purify the all files in directory paths: glob. Sync (\ ` \ ${path. Resolve (__dirname, '.. / main ')} / * * / * \ `, {nodir: true}) }, //...]Copy the code
After optimization, the CSS is removed from the main package, and the main package is suddenly smaller
Also, the CSS we packaged was only 3KB, or closer to 200KB if we packaged it in full
HTML compression
The htML-webpack-plugin automatically enables minify when production is true if mode is ‘production’, otherwise false
Image compression (official website copy configuration option)
{ test: /\\.(gif|png|jpe? g|svg)\$/i, use:[ 'file-loader', { loader: 'image-webpack-loader', options: { mozjpeg: { progressive: true, }, // optipng.enabled: false will disable optipng optipng: { enabled: false, }, pngquant: { quality: [0.65, 0.90], speed: 4}, GIFsicle: {interlaced: false,}, // The webp option will enable webp webp: {lace: 75}}},]}Copy the code
Tree Shaking, only using methods into bundles (taking advantage of es6 module features)
Production mode is enabled by default
The code segment
-
Entry point segmentation
-
If there are duplicate modules (Lodash) between the chunks, those duplicate modules will be introduced into each bundle
-
Not flexible enough and unable to dynamically split code from core application logic
entry: { index: “./src/index.js”, login: “./src/login.js” }
Dynamic import and lazy load
-
Split code needs an on-demand loading opportunity
-
For the first time the page needs to be loaded directly, as soon as possible to show the user, some of the function points that rely on a lot of code can be loaded on demand
-
The functions of the website are divided into chunks for each category. Enabling splitChunks will create separate files
-
You can also write magic notes
document.querySelector(‘#clickBtn’).addEventListener(‘click’,() => { import(‘./hello’).then(result => { console.log(result.default); }); });
According to the need to load
The React project implements on-demand loading: react.lazy
use
const MyLazy = React.lazy(()=>import('./MyLazy'))
Copy the code
MyLazy must use Suspense wrapping and fallback is the loading component
<Suspense fallback={<div>loading</div>}>
<MyLazy />
</Suspense>
Copy the code
Preload
- Preload is usually used for key resources used in the page, including key JS, fonts, and CSS files
- Preload will increase the load order weight of resources, so that key data can be downloaded in advance, optimize the speed of page opening
- By adding a preloaded comment to a resource, you indicate that the module needs to be used immediately
Asynchronous/delayed/inserted scripts (regardless of location) are Low in network priority
<link rel="preload" as="script" href="utils.js">
import(
\`./utils.js\`
/* webpackPreload: true */
/* webpackChunkName: "utils" */
)
Copy the code
-
prefetch
- Different from preload, prefetch tells the browser about a resource that is likely to be used in the future, and the browser will load the corresponding resource when it is free. If the user’s behavior can be predicted, such as lazy loading or clicking on another page, it is equivalent to preloading the required resource in advance
<link rel="prefetch" href="utils.js" as="script">
button.addEventListener('click', () => { import( \`./utils.js\` /* webpackPrefetch: true */ /* webpackChunkName: "utils" */ ).then(result => { result.default.log('hello'); })});Copy the code
Preload is used for resources that are necessary for the current page and Prefetch is used for resources that may be used in future pages
splitChunks
Starting with WebPack V4, CommonsChunkPlugin has been removed and replaced with Optimization.splitchunks.
configuration
Optimization: {// Enable minimization, the default is true minimize: true, minimizer: [new TerserPlugin({ exclude:/nodu_modules/ })], // splitChunks:{ chunks: The default value is all/initial/async minSize: 0, and the default value is 30KB, minChunks of the smallest size: MaxAsyncRequests: // How many modules are shared, and how many times modules are referenced before splitting // limit the number of parallel requests per import(maxInitialRequests: 5) // limit the number of parallel requests per import(maxInitialRequests: 5) By default, the name of chunk is separated by a separator (~ by default), such as vendor~}},Copy the code
Configure the splitChunks package file as shown in the figure above.
unfinished
- Javascript compatibility
- Because we use
ts-loader
Compile TSX directly without using Babel, so no consideration
- Because we use