- The world’s martial arts, only fast can not break
- New features
- Get in the car. Get ready for the upgrade
- Upgrade pothole avoidance guide
- Continue to accelerate
- Analysis of compilation results
- Tree -shaking, constrained coding
- At the end
For today’s front-end projects, compiling and publishing is almost a must, from lightning-fast compilations in a few seconds to snail’s pace compilations in 10 minutes or more. Especially when it comes to online hot fixes, every second counts and the response speed directly affects the user experience. Users won’t have the patience to wait so long for you to compile slowly. If the payment operation is involved, the product loss is measured in seconds, every one second ahead of the release, in front of Tencent’s massive users, can recover a large loss. In addition, the improvement of compilation efficiency brings the most direct benefit is the double improvement of development efficiency and development experience.
So what’s slowing down WebPack packaging, and what can we do to improve it?
Webpack is a very popular packaging tool at present. As of 6 days ago, WebPack 4 has been updated to version 4.28.3. In 10 months, the small version has been updated dozens of times, which shows the prosperity of the community.
When webpack4 was released, it was reported to be 60% to 98% faster.
The world’s martial arts, only fast can not break
Since the local project was upgraded to Webpack 4 a few months ago, webpack was manually downgraded to version 3.12.0 in order to obtain test data, and the rest of the configuration was left largely unchanged.
During the test, Mac only ran common IM, mailbox, terminal, browser, etc. In order to avoid the impact of plug-ins on data as much as possible, I closed some optimized plug-ins and reserved only common Loader and JS compression plug-ins.
The following are screenshots of five tests in [email protected] and [email protected] respectively.
Data analysis is as follows (unit: ms) :
The first | The 2nd | The third time | 4 times | The fifth time | On average, | Speed up | |
---|---|---|---|---|---|---|---|
webpack3 | 58293 | 60971 | 57263 | 58993 | 60459 | 59195.8 | – |
webpack4 | 42346 | 40386 | 40138 | 40330 | 40323 | 40704.6 | 45% |
Pure version upgrade, the compilation speed is increased to 45%. Here, I select mature online running projects, and the improvement of construction speed is meaningful only based on mature projects. Demo project is difficult to reflect the complexity of the construction environment due to the small compilation file base, and there may be large errors in testing. Meanwhile, the gap with the official data is mainly due to different projects and configurations.
Either way, the nearly 50% increase in compile speed is worth trying to upgrade webpack4! Of course, optimization has just begun, so read on.
New features
In order to upgrade webpack4 more smoothly, we need to understand it first.
Webpack4 introduces several new features while greatly improving compilation efficiency:
-
/ SRC/directory, default entry./ SRC /index.js, default output./dist directory, default output. The default output file is./dist/main.js.
-
Out of the box with WebAssembly, WebPack4 provides WASM support and can now import and export any WebAssembly module, as well as write a loader to import C++, C, and Rust. (Note: WebAssembly modules can only be used in asynchronous chunks)
-
Provide the mode attribute, set to Development for the best development experience, and set to Production for project compilation and deployment, such as enabling Scope and tree-shaking.
-
The new plugin system provides new apis for plugins and hooks, with the following changes:
- All hooks are managed by hooks objects, which treat all hooks as extendable class attributes
- To add a plug-in, you need to provide a name
- When developing a plug-in, you can choose the type of plug-in (sync/callback/promise)
- Hooks = {myHook: new SyncHook(…); } to register the hook
For more information on how plug-ins work, see how the New plug-in system works.
Get in the car. Get ready for the upgrade
First, the webpack-dev-server plug-in needs to be updated to the latest, and webpack-CLI is also required because webpack-CLI takes on the webpack4 command line related functions.
The mode attribute must be specified, otherwise convention takes precedence over configuration, and the production environment will be compiled by default.
WARNING in configuration The ‘mode’ option has not been set, Webpack will fallback to ‘production’ for this value. Set ‘mode’ option to ‘development’ or ‘production’ to enable Defaults for each environment. You can also set it to ‘none’ to disable any default behavior. Learn more: Webpack.js.org/concepts/mo…
There are two ways to add a mode configuration.
-
Specify — mode in package.json script:
"scripts": { "dev": "webpack-dev-server --mode development --inline --progress --config build/webpack.dev.config.js"."build": "webpack --mode production --progress --config build/webpack.prod.config.js" } Copy the code
-
Add the mode attribute to the configuration file
module.exports = { mode: 'production' / / or development }; Copy the code
After upgrading to webpack4, some of the default plug-ins have been replaced by the optimization configuration as follows:
- CommonsChunkPlugin abandoned by optimization. SplitChunks and optimization. RuntimeChunk substitution, the former split code, which extract the runtime code. The original CommonsChunkPlugin produced modules with duplicate code and could not optimize asynchronous modules. The configuration of Minitchunks was also complicated. SplitChunks solved this problem. . In addition, the optimization runtimeChunk is set to true (or {name: “manifest”}), will have to pick up the entrance to the module of the runtime.
- NoEmitOnErrorsPlugin abandoned by optimization noEmitOnErrors alternative, production environment opened by default.
- NamedModulesPlugin abandoned by optimization namedModules alternative, production environment opened by default.
- ModuleConcatenationPlugin abandoned by optimization concatenateModules alternative, production environment opened by default.
- Optimize.UglifyJsPlugin obsolete, optimization. Minimize replacement, production environment default on.
Not only that, Optimization provides the following default configuration:
optimization: {
minimize: env === 'production' ? true : false.// Development environment does not compress
splitChunks: {
chunks: "async".// Three values are available: initial(initial module), async(on-demand module), and all(all module)
minSize: 30000.// Modules over 30K are automatically removed to public modules
minChunks: 1.// If the module is referenced >= once, it is split
maxAsyncRequests: 5.// The number of concurrent requests for asynchronously loading chunk <=5
maxInitialRequests: 3.// The number of chunks loaded concurrently in an entry <=3
name: true.// The default module name is hash. Multiple modules with the same name will be merged into one
automaticNameDelimiter: '~'.// Name the delimiter
cacheGroups: { // The cache group inherits and overwrites the splitChunks configuration
default: { // Module cache rule, set to false, default cache group will be disabled
minChunks: 2.// The modules are referenced >=2 times, and are then split into vendors public modules
priority: - 20./ / priority
reuseExistingChunk: true.// Use existing modules by default
},
vendors: {
test: /[\\/]node_modules[\\/]/.// splits modules in node_modules by default
priority: - 10}}}}Copy the code
SplitChunks are important to unpack and optimize. If your project contains third-party components such as Element-UI (large components), it is recommended to unpack them separately, as shown below.
splitChunks: {
// ...
cacheGroups: {
elementUI: {
name: "chunk-elementUI".// Unpack elementUI separately
priority: 15.// The weight must be greater than other cache groups
test: /[\/]node_modules[\/]element-ui[\/]/}}}Copy the code
For more usage, see the comments above or the official documentation SplitChunksPlugin.
Upgrade pothole avoidance guide
Webpack 4 no longer supports Node 4. Due to the new JavaScript syntax, Tobias, one of the founders of Webpack, recommends that users use Node version >= 8.94 for optimal performance.
There are all kinds of errors you can encounter after a formal upgrade, of which the following are common.
VueLoaderPlugin needs to be added to the Webpack for VUE-Loader V15.
const { VueLoaderPlugin } = require("vue-loader"); // const VueLoaderPlugin = require("vue-loader/lib/plugin"); // The two are equivalent
/ /...
plugins: [
new VueLoaderPlugin()
]
Copy the code
After upgrading to WebPack4, the mini-CSS-extract-Plugin replaces the extract-text-webpack-plugin as the preferred CSS packaging option, which has the following advantages:
- Asynchronous loading
- Better performance without recompiling
- Easier to use
Bug that does not support CSS hot updates. Therefore, csS-hot-loader should be introduced in the development environment to support CSS hot update, as shown below:
{
test: /\.scss$/,
use: [
...(isDev ? ["css-hot-loader"."style-loader"] : [MiniCssExtractPlugin.loader]),
"css-loader",
postcss,
"sass-loader"]}Copy the code
The CSS is optimized for compression before it is released to production, using the optimization-CSS-assets-webpack-plugin.
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
/ /...
plugins: [
new OptimizeCssAssetsPlugin({
cssProcessor: cssnano,
cssProcessorOptions: {
discardComments: {
removeAll: true}}})]Copy the code
Continue to accelerate
At the beginning of this article, I mentioned that optimization is just beginning. Yes, as projects get more complex and Webpack gets slower, there must be a way to squeeze performance even further.
After running and testing multiple projects over a long period of time, the following lessons have worked well.
-
Reduce the compilation scope and reduce unnecessary compilation work. That is, modules, mainFields, noParse, includes, exclude, and alias are all used.
const resolve = dir => path.join(__dirname, '.. ', dir); // ... resolve: { modules: [ // Specify the following directories to find third-party modules to avoid Webpack's recursive search to the parent directory resolve('src'), resolve('node_modules'), resolve(config.common.layoutPath) ], mainFields: ['main'].// Only use the main field as the entry file description field to reduce the search steps alias: { vue$: "vue/dist/vue.common"."@": resolve("src") // Cache SRC directories as @ symbols to avoid repeated addressing}},module: { noParse: /jquery|lodash/.// Ignore files that are not modularized, so jquery or LoDash will not be parsed by loaders below // noParse: function(content) { // return /jquery|lodash/.test(content) // }, rules: [ { test: /\.js$/, include: [ // Only the following directories are parsed, reducing the processing scope of the Loader resolve("src"), resolve(config.common.layoutPath) ], exclude: file => /test/.test(file), // Exclude the test directory file loader: "happypack/loader? id=happy-babel" // More on that later]}},Copy the code
-
If you want to further improve compilation speed, you need to know where the bottleneck is? Through the test, it is found that there are two slow stages: ① Babel loaders parsing stage; ② JS compression stage. Loader parsing will be discussed later, and JS compression is the last stage of publishing and compiling. Usually webPack needs to be stuck for a while. This is because js compression requires the code to be parsed into the AST syntax tree, then the AST needs to be analyzed and processed according to complex rules, and finally the AST is returned to JS. This process is computationally intensive and therefore time-consuming. As shown below, compilation looks stuck.
In fact, with the Webpack-parallel-Ugliffe -plugin, the process can be sped up twice as fast. We all know that Node is single-threaded, but node can fork a task. Based on this, webpack-parallel-uglip-plugin can split the task into multiple sub-processes for concurrent execution, and then send the result to the main process, thus implementing concurrent compilation. In this way, js compression speed is greatly improved. The configuration is as follows.
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin'); // ... optimization: { minimizer: [ new ParallelUglifyPlugin({ // Multi-process compression cacheDir: '.cache/', uglifyJS: { output: { comments: false, beautify: false }, compress: { warnings: false, drop_console: true, collapse_vars: true, reduce_vars: true}}}),]}Copy the code
Of course, I tested five sets of data, and here are the screenshots:
Data analysis is as follows (unit: ms) :
The first | The 2nd | The third time | 4 times | The fifth time | On average, | Speed up | |
---|---|---|---|---|---|---|---|
webpack3 | 58293 | 60971 | 57263 | 58993 | 60459 | 59195.8 | – |
Webpack3 comes with the ParallelUglifyPlugin | 44380 | 39969 | 39694 | 39344 | 39295 | 40536.4 | 46% |
webpack4 | 42346 | 40386 | 40138 | 40330 | 40323 | 40704.6 | – |
Webpack4 comes with ParallelUglifyPlugin | 31134 | 29554 | 31883 | 29198 | 29072 | 30168.2 | 35% |
With the webpack-parallel-Ugli-fi plugin, the build speed of WebPack3 can be increased by 46%. Even after upgrading to webpack4, the build speed increased by a further 35%.
-
Now let’s see how the loader parsing speed can be improved. HappyPack, like the webpack-parallel-uglip-plugin, can compile concurrently, which can greatly improve the speed of loader parsing.
const HappyPack = require('happypack'); const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length }); const createHappyPlugin = (id, loaders) => new HappyPack({ id: id, loaders: loaders, threadPool: happyThreadPool, verbose: process.env.HAPPY_VERBOSE === '1' // make happy more verbose with HAPPY_VERBOSE=1 }); Copy the code
Loader: “happypack/loader? Id =happy-babel”, you need to create a happy-babel plug-in instance in your plugins.
plugins: [ createHappyPlugin('happy-babel', [{ loader: 'babel-loader', options: { babelrc: true, cacheDirectory: true // Enable caching}}]]Copy the code
HappyPack starts 3 processes (default number of cpus -1).
In addition, vue-loader and CSS-loader support happyPack acceleration, as shown below.
plugins: [ createHappyPlugin('happy-css', ['css-loader', 'vue-style-loader']), new HappyPack({ loaders: [{ path: 'vue-loader', query: { loaders: { scss: 'vue-style-loader!css-loader!postcss-loader!sass-loader?indentedSyntax' } } }] }) ] Copy the code
Based on WebPack4, equipped with Webpack-parallel-Ugli-fi – Plugin and happyPack plug-in, test screenshots are as follows:
Data analysis is as follows (unit: ms) :
The first | The 2nd | The third time | 4 times | The fifth time | On average, | Speed up | |
---|---|---|---|---|---|---|---|
Only carry ParallelUglifyPlugin | 31134 | 29554 | 31883 | 29198 | 29072 | 30168.2 | 35% |
ParallelUglifyPlugin and happyPack | 26036 | 25884 | 25645 | 25627 | 25794 | 25797.2 | 17% |
It can be seen that the happyPack plugin can still improve the compilation speed by 17% on the basis of the webpack-parallel ugli-fi -plugin. In fact, because sass and other loaders do not support happyPack, HappyPack still has room for improvement. For more information, please refer to happypack.
-
As we all know, when webpack is packaged, some framework code is basically unchanged, such as Babel-Polyfill, vue, vuex, AXIos, Element-UI, Fastclick, etc. These modules also have a large size. Each compilation must be loaded once, which is time-consuming and laborious. These modules can be pre-packaged using the DLLPlugin and DLLReferencePlugin plug-ins.
To complete the DLL process, we need to prepare a new WebPack configuration, webpack.dll.config.js.
const webpack = require("webpack"); const path = require('path'); const CleanWebpackPlugin = require("clean-webpack-plugin"); const dllPath = path.resolve(__dirname, ".. /src/assets/dll"); // Directory for storing DLL files module.exports = { entry: { // Put vUE related modules into a separate dynamic link library vue: ["babel-polyfill"."fastclick"."vue"."vue-router"."vuex"."axios"."element-ui"] }, output: { filename: "[name]-[hash].dll.js"./ / generated vue. DLL. Js path: dllPath, library: "_dll_[name]" }, plugins: [ new CleanWebpackPlugin(["*.js"] and {// Clear the previous DLL file root: dllPath, }), new webpack.DllPlugin({ name: "_dll_[name]".// manifest.json describes what the dynamic link library contains path: path.join(__dirname, ". /"."[name].dll.manifest.json")})]};Copy the code
Next, you need to add the DLL command to package.json.
"scripts": { "dll": "webpack --mode production --config build/webpack.dll.config.js" } Copy the code
- 1
- 2
- 3
After running NPM run DLLS, generates the. / SRC/assets/DLL/vue. DLL – [hash]. Public js and js. / build/vue. DLL. The manifest. Json resource description file, thus the DLL preparation is complete, Then reference it in wepack.
externals: { 'vue': 'Vue'.'vue-router': 'VueRouter'.'vuex': 'vuex'.'elemenct-ui': 'ELEMENT'.'axios': 'axios'.'fastclick': 'FastClick' }, plugins: [ ...(config.common.needDll ? [ new webpack.DllReferencePlugin({ manifest: require("./vue.dll.manifest.json"})] : []]Copy the code
DLL public js does not change easily, and if an update does occur in the future, the new DLL file name will need to be added with the new hash, so that the browser does not cache the old file and cause execution errors. Because of the uncertainty of the hash, there is no way to specify a fixed link script in the HTML entry file, but the add-asset-html-webpack-plugin automatically imports DLL files.
const autoAddDllRes = () => { const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin'); return new AddAssetHtmlPlugin([{ // Inject DLL js into HTML publicPath: config.common.publicPath + "dll/".// The path injected into the HTML outputPath: "dll".// Final output directory filepath: resolve("src/assets/dll/*.js"), includeSourcemap: false, typeOfAsset: "js" // options js, CSS; default js }]); }; // ... plugins: [ ...(config.common.needDll ? [autoAddDllRes()] : []) ] Copy the code
With the DLL plug-in, the compilation speed of Webpack4 is further improved, as shown in the screenshot below:
Data analysis is as follows (unit: ms) :
The first | The 2nd | The third time | 4 times | The fifth time | On average, | Speed up | |
---|---|---|---|---|---|---|---|
ParallelUglifyPlugin and happyPack | 26036 | 25884 | 25645 | 25627 | 25794 | 25797.2 | 17% |
ParallelUglifyPlugin, happyPack, and DLL are available | 20792 | 20963 | 20845 | 21675 | 21023 | 21059.6 | 22% |
It can be seen that the compilation speed of Webpack4 can still be improved by 22% with DLL installed.
To sum up, we summarize the above multiple times of data and get the following table:
The first | The 2nd | The third time | 4 times | The fifth time | On average, | Speed up | |
---|---|---|---|---|---|---|---|
webpack3 | 58293 | 60971 | 57263 | 58993 | 60459 | 59195.8 | – |
webpack4 | 42346 | 40386 | 40138 | 40330 | 40323 | 40704.6 | 45% |
ParallelUglifyPlugin, happyPack, and DLL are available | 20792 | 20963 | 20845 | 21675 | 21023 | 21059.6 | 181% |
ParallelUglifyPlugin, happyPack, and DLL plugins can improve the compile speed by 181% and reduce the overall compile time by nearly two-thirds. And as the project progresses, this compilation improvement becomes more and more noticeable.
In fact, in order to obtain the above test data, I turned off the cache of Babel and ParallelUglifyPlugin. After enabling the cache, the average compile time of the second time was 12.8s, which is 362% faster than webPack3 due to the previous cache. Even if you’ve upgraded to webpack4, you’ll still see a 218% increase in compile speed with all three plugins listed above!
Analysis of compilation results
Of course, compile speed is an indicator that affects the developer experience more than the compiled file size. Webpack4 compiles files that are slightly smaller than the previous version. In order to better track file size changes, both development and production environments need to introduce the WebPack-bundle-Analyzer plug-in, as shown below.
The file size is as shown below:
Tree -shaking, constrained coding
sideEffects
Since webpack2, tree-shaking is used to eliminate useless modules, relying on the static structure of ES Modules, and turning on useless Module detection by setting “modules”: false in the.babelrc file, which is relatively rough. Webapck4 provides a flexible extension of garbage detection, mainly by setting sideEffects: false in package.json to tell the compiler that the project or module is pure and that garbage can be removed.
For tree-shaking to really work, when importing resources, it is important to import only the required components, as shown below:
import { Button, Input } from "element-ui"; // Import only required components
Copy the code
At the end
In the process of upgrading webpack4, stomping pits is a must, the key is stomping pits, what do you get?
In addition, in addition to some optimization methods introduced in the article, more optimization strategies are being verified step by step…