This article is licensed under a “CC BY 4.0” license. You are welcome to reprint or modify this article, but the source must be noted.
Author: Baiying front end team @ Pisces
1. Core concepts
- Entry: indicates the entrance. Webpack is based on modules. To use WebPack, you first need to specify module parsing entries. From the entry, WebPack recursively parses and processes all resource files based on inter-module dependencies.
- Output: indicates output. The final product after the source code has been processed by WebPack.
- Loader: indicates the module converter. It is essentially a function that converts the received content and returns the converted result. Because Webpack only knows JavaScript, the Loader acts as a translator, preprocessing other types of resources for translation.
- Plugin: extension plug-in. Based on the event flow framework
Tapable
Plugins can extend the functionality of Webpack by listening for events that are broadcast during the life of Webpack and changing the output when appropriate using the apis provided by Webpack. - Module: module. In addition to es Module, commonJs, AMD, etc., CSS @import, URL (…) , images, fonts, and so on are all considered modules in WebPack.
In addition, webpack4 mode becomes an important concept, webpack provides some default values for different mode, attached teacher Ruan Yifeng’s fun
The default configurations for different modes are as follows:
2. Packaging process
- Initialization parameters: read and merge parameters from configuration files and Shell statements to get the final parameters;
- Initialize compilation: The parameters obtained in the previous step initialize the Compiler object, register the plug-in, and pass in the Compiler instance (with many WebPack event apis mounted for the plug-in to call);
- AST & dependency graph: Starting from the entry file, call the AST engine (ACORN) to generate the abstract syntax tree AST, and build all the dependencies of the module according to the AST;
- Recursively compiling modules: all configured loaders are called to compile modules;
- Output resources: According to the dependencies between the entry and the module, assemble them into chunks containing multiple modules, and then convert each Chunk into a separate file and add it to the output list. This step is the last chance to modify the output content.
- Output completion: After the output content is determined, the output path and file name are determined according to the configuration, and the file content is written to the file system.
In the above process, Webpack will broadcast specific events at specific points in time, the plug-in will execute specific logic after listening to the relevant events, and the plug-in can call the API provided by Webpack to change the Webpack running result
Core concepts of the construction process:
- Tapable: a publish-subscription-based event flow utility class. Compiler and Compilation objects are inherited from Tapable
- Compiler: The core object that webPack builds throughout, a global singleton that is created during the compilation initialization phase and contains complete configuration information, loaders, plugins, and various tool methods
- Compilation: In watch mode, each recompilation triggered by a file change will generate a new Compilation object, including the currently compiled module, compiled resources, changed files, dependent states, etc
More detailed construction flow chart:
For a larger view, go here to 👈
Flow chart source: Tao department front end team – a detailed webpack process
3. Loader
Loader, like a translator, converts source files into object files and sends them to the next process
Method of use
- Each loader has a single responsibility, just like a worker on an assembly line
- The order is critical (right to left)
Implementation guidelines
- Simple the loader performs only a single task. Multiple Loaders > one multi-function Loader
- Chaining follows the Chaining rule
- Stateless. Pure Function. No side effects
- Use the Loader Utilities library to take full advantage of the Loader-utils package
Implement a simple loader that replaces console.log, removes newlines, and adds a custom line at the end of the file
/** webpack.config.js */
const path = require("path");
module.exports = {
entry: {
index: path.resolve(__dirname, "src/index.js"),
},
output: {
path: path.resolve(__dirname, "dist"),
},
module: {
rules: [
{
test: /\.js$/.
use: [
{
loader: path.resolve("lib/loader/loader1.js"),
options: {
message: "this is a message".
}
}
].
},
].
},
};
Copy the code
/** lib/loader/loader1.js */
const loaderUtils = require('loader-utils');
/** Filter console.log and newline */
module.exports = function (source) {
// Obtain the Loader configuration item
const options = loaderUtils.getOptions(this);
console.log(Loader configuration item:, options);
const result = source
.replace(/console.log\(.*\); ? /g."")
.replace(/\n/g."")
.concat(`console.log("${options.message || 'No configuration item'}"); `);
return result;
};
Copy the code
How to write an asynchronous Loader
/** lib/loader/loader1.js */
/** Asynchronous loader */
module.exports = function (source) {
let count = 1;
// 1. Call this.async() to tell webPack that this is an asynchronous Loader and needs to wait for the asyncCallback callback before moving on to the next loader
This. async returns an asynchronous callback. The call indicates that the asynchronous Loader has finished processing
const asyncCallback = this.async();
const timer = setInterval((a)= > {
console.log(Time has passed${count++}Second `);
}, 1000);
// Asynchronous operation
setTimeout((a)= > {
clearInterval(timer);
asyncCallback(null, source);
}, 3200);
};
Copy the code
4. Plugin
Perform specific functions at specific nodes throughout the WebPack compilation lifecycle
Key points to achieve:
- A named JS function or JS class
- Define an Apply method on Prototype (for WebPack to call and inject the Compiler object when called)
- You need to have webPack event hooks in the Apply function that are mounted via the Compiler object. (The hook function can retrieve the compilation object that is currently being compiled.)
- Handle specific data for webPack internal instances
- When the functionality is complete, call the callback provided by WebPack
Basic model:
// 1, Plugin name
const MY_PLUGIN_NAME = "MyBasicPlugin";
class MyBasicPlugin {
// 2. Get the plug-in configuration item in the constructor
constructor(option) {
this.option = option;
}
// 3. Define an apply function on the prototype object for WebPack to call
apply(compiler) {
// 4, register webPack event listener function
compiler.hooks.emit.tapAsync(
MY_PLUGIN_NAME,
(compilation, asyncCallback) => {
// 5. Operate Or change the internal Compilation data
console.log(compilation);
console.log("Current stage ======> Compile completed, will be output to the output directory");
// 6. If it is an asynchronous hook, perform an asynchronous callback at the end
asyncCallback();
}
);
}
}
// 7
module.exports = MyBasicPlugin;
Copy the code
Implement a plugin that automatically generates README files in the dist directory:
const MY_PLUGIN_NAME = "MyReadMePlugin";
// Plugin functionality: Automatically generates a README file with the title taken from plugin Option
class MyReadMePlugin {
constructor(option) {
this.option = option || {};
}
apply(compiler) {
compiler.hooks.emit.tapAsync(
MY_PLUGIN_NAME,
(compilation, asyncCallback) => {
compilation.assets["README.md"] = {
// File content
source: (a)= > {
return ` #The ${this.option.title || 'Default title'}`;
},
// File size
size: (a)= > 30.
};
asyncCallback();
}
);
}
}
// 7
module.exports = MyReadMePlugin;
Copy the code
Compiler. hooks are attached to WebPack event functions that are fired at different times (similar to the React life cycle) and can perform other logic or change the output at various stages of compilation. For a detailed list of supported events, see compiler-hooks
Tapable:
Webpack’s plug-in architecture is based on Tapable, an internal library of the Webpack project that abstracts a set of plug-in mechanisms. It is similar to NodeJS’s EventEmitter class, focusing on the firing and manipulation of custom events.
Tapable events are classified into synchronous and asynchronous types, and are internally classified into different types by different rules. The specific differences of the above events can be seen in this article. Understanding the differences and application scenarios of these events will help us understand webPack source code and write plugins
The object of Complier:
It is initialized once at WebPack startup. It is globally unique and can be understood as a webPack compiled instance, which contains the original WebPack configuration, Loader, Plugin reference, and various hooks
Source: https://github.com/webpack/webpack/blob/10282ea20648b465caec6448849f24fc34e1ba3e/lib/webpack.js
5. Optimize performance
1. Where to Start?
-
Use the speed-measurement-webpack-plugin to measure the packing speed
-
Use Webpack-bundle-Analyzer for volume analysis
An obvious area of improvement from a project’s diagrams is that BizCharts are not introduced as needed. In this case, we can import the path and perform the packaging analysis again to see the effect.
In addition, each module in the figure has three sizes, namely Stat Size, Parsed Size and Gzipped Size. What are the meanings of these three sizes? Please check the Github issue of the plug-in
2. Optimize Loader configuration
The main idea is to optimize the search time, narrow the search scope of files, and reduce unnecessary compilation work. For details, see the following configuration files
module .exports = {
module : {
rules : [{
// If the project source contains only files, do not write /\ JSX? $/ to improve regular expression performance
test: /\.js$/.
// Babel-Loader supports caching of translated results, enabled with the cacheDirectory option
use: ['babel-loader? cacheDirectory'].
// Use babel-Loader only for files in the SRC directory under the project root
include: path.resolve(__dirname,'src'),
// Use resolve.alias to map the original import path to a new one, reducing time-consuming recursive resolution operations
alias: {
'react': path.resolve( __dirname ,'./node_modules/react/dist/react.min.js'),
},
// Let Webpack ignore recursive parsing of files that are not modularized
noParse: '/jquery|lodash/'.
}].
}
}
Copy the code
3. DLL Plugin Or Externals
Use DLLPlugin to move code that changes less frequently (third-party libraries) to a separate compilation. I understand that in most scenarios, the function is similar to that of configuring externals (no third-party libraries to be packaged), but externals may fail in some scenarios. See this article for more details. In addition, DLLPlugin specific use reference here
4. Multi-process series
There are several well-known players in the multi-process camp:
- Thread-loader (official recommendation after V4)
- Happypack (not much maintenance anymore)
- Parallel – WebPack (not much maintenance anymore)
This section introduces only thread-loaders. When using thread-loaders, the loader with high overhead (such as Babel-Loader) is placed in an independent process (officially described as worker Pool)
-
Place it before the loader that needs to be loaded separately, and the order is critical
module.exports = {
module: {
rules: [
{
test: /\.js$/.
include: path.resolve("src"),
use: [
"thread-loader".
// your expensive loader (e.g babel-loader)
]
}
]
}
}
Copy the code -
Loader usage in worker Pool is limited. For example, custom Loader API cannot be used and webPack configuration items cannot be obtained
5. Use cache wisely to reduce non-first build time
At present, the project is using the plug-in of hard-source-webpack-plugin, which is quite effective, but there are 3 disadvantages
- The generated cache files are large, which occupies disk space (there have been cases before where the cache files were uploaded to the server by mistake during the publishing process, resulting in a very slow publishing process =. =, so it’s better to specify the cache file path inside node_modules.
- This warehouse hasn’t been updated in a while
- Occasionally, existing projects have changed code that does not trigger recompilation, presumably because of this plug-in
In addition, whether WebPack5 has a built-in caching policy or an officially maintained cache plug-in still needs to know
6. Code compression reduces product volume
-
The WebPack3 configuration optimization. Minimize = true will enable UglifyJsPlugin by default, and its multi-process version is ParallelUglifyPlugin
-
Webpack4 in webpack. Optimize. UglifyJsPlugin abandoned, use the default built-in terser webpack – plugin plug-in compression optimization code, Native support for multiple processes (remember the first optimization measure listed in the Build Performance section of the official document: Stay Up to Date, the most beautiful is the latest webPack version)
7. Code Splitting
Code splitting describes three positions:
-
Multi-entry configuration (Multiple entries is natural code splitting, but almost no one should change a single-page application into multiple entries because of performance optimization points)
-
SplitChunksPlugin is used for deduplication and extraction
-
Dynamic Import is used to specify module split, and more user experience optimization can be done with preload and prefetch
6. Think bigger: Questions worth probing 🤔
- How does HMR work?
- Tree Shaking principle: Why do we need es Module writing?
- What are the advantages of Module Federation for webpack5, what are the interesting things in conjunction with http2.0, and the application on the micro front end?
- Why is Rollup better than WebPack for packaging component libraries?