The full text is 2500 words, and the reading time is about 30 minutes. If you feel the article useful, welcome to praise attention, but writing is not easy, without the consent of the author, prohibit any form of reprint!!
background
As mentioned in the previous series of articles, raw resource modules exist, flow, and parse as Module objects in webPack implementations.
Chunk is the basic organization unit of output products. In the generation phase, Webpack inserts entry and other modules into Chunk according to rules. The SplitChunksPlugin plug-in then performs a series of changes, disassembly, and merging operations on the Chunks based on optimization rules and ChunkGraph to reorganize them into Chunks with (possibly) higher performance. After running, Webpack continues to write chunk one by one into the physical file to complete compilation.
To sum up, Module mainly plays a role in the first half of webpack compilation process to solve the problem of “how to read” original resources; While Chunk objects mainly play a role in the second half of compilation to solve the problem of “how to write” the compilation product. The two work together to build the main process of Webpack construction.
Chunk arrangement rules are very complex, involving entry, optimization and many other configuration items. I plan to divide it into two articles to explain basic subcontracting rules and SplitChunksPlugin subcontracting optimization rules respectively. This article will focus on the first part. Explains the details and principles of entry, asynchronous module, and Runtime rules.
Default subcontracting rule
After Webpack 4, the compilation process can be broken down into four stages:
In the make stage, webpack starts from entry and builds the ModuleDependencyGraph step by step according to the require/import relationship between modules. The dependency graph expresses the order of mutual reference between modules. Based on this kind of order webpack can infer the module need to perform before those who rely on module, also can further deduce the module should be packaged together, the module can delay the loading (asynchronously), for more information about the module dependency graph, you can refer to my another article “difficult webpack knowledge: Dependency Graph in Depth.
In the build phase (SEAL), WebPack organizes the Chunk objects to be subcontracted according to the contents of the module dependency graph. The default subcontracting rules are:
- The same
entry
The next touched module is organized into a chunk - Asynchronous modules are individually organized into a chunk
entry.runtime
Organized into a single chunk
The default rules are implemented in the compiling. seal function. After the seal core logic is run, a series of Chunk, ChunkGroup, and ChunkGraph objects will be generated. Subsequently, SplitChunksPlugin plug-in will further disassemble and optimize the Chunk series objects, and finally reflect the output to show complex subcontracting results.
Let’s talk about default generation rules.
Entry subcontract processing
Key: The SEAL phase traverses the entry objects and generates chunk for each entry. Then, all modules that an entry touches are packaged into chunk based on the module dependency diagram.
In the build phase, Webpack first creates a Chunk object for each entry based on iterating through the entry attribute values provided by the user, for example, for the following configuration:
module.exports = {
entry: {
main: "./src/main",
home: "./src/home",
}
};
Copy the code
Webpack iterates through the attributes of the Entry object and creates two objects, chunk[main] and chunk[home]. In this case, the two chunks contain main and home modules respectively:
After the initialization, Webpack will read ModuleDependencyGraph content, the content of the entry into the corresponding the chunk (in Webpack/lib/buildChunkGrap js file). For example, for the following file dependencies:
ModuleDependencyGraph analysis process will gradually add a/ B/C/D module to chunk[main], and finally form:
PS: Chunk generated based on dynamic loading is usually called in the official Webpack documentationInitial chunk 。
Asynchronous module subcontracting processing
Important: When analyzing the ModuleDependencyGraph, a separate Chunk object will be created for each asynchronous module encountered and the asynchronous module will be packaged separately.
After Webpack 4, modules can be dynamically loaded by simply introducing them with asynchronous statements require.ensure(“./xx.js”) or import(“./xx.js”), which is essentially chunk-based.
In the Webpack generation stage, when an asynchronous import statement is encountered, a separate chunk object will be generated for the module and its sub-modules will be added to this chunk. For example, for the following example:
// index.js, entry file
import 'sync-a'
import 'sync-b'
import('async-c')
Copy the code
In index.js, sync-a and sync-b are introduced synchronously; Introduce async-A module in asynchronous mode; At the same time, · modules are introduced synchronously in ·. Corresponding module dependencies are as follows:
At this point, Webpack will create subcontracts for the entry index.js and asynchronous module async-A.js respectively, forming the following data:
A new concept needs to be introduced here — the father-child relationship between chunks. The Chunk generated by entry is isolated from each other and has no necessary dependency relationship. However, the Chunk generated asynchronously is different. The referent (the index.js block above) needs to use the referent (the async-A block above) in specific scenarios. In webpack, the referenced parent and referenced child are stored in the properties of chunkgroup. _parents and chunkgroup. _children respectively.
The above subcontracting scheme generates two files by default:
- The entrance
index
The correspondingindex.js
- Asynchronous module
async-a
The correspondingsrc_async-a_js.js
At runtime, webpack loads and runs the file src_async-a_js.js asynchronously in index.js using promise and the __webpack_require__.e method to achieve dynamic loading.
PS: Chunk based on asynchronous modules in the official Webpack documentation, commonly referred to asAsync chunk 。
The Runtime of the subcontract
Important: After Webpack 5, runtime code can be packaged separately based on the Entry.Runtime configuration.
In addition to Entry, asynchronous modules, WebPack 5 also supports runtime-based subcontracting rules later. In addition to business code, Webpack compilation products also need to include some supporting code to support Webpack modularity, asynchronous loading and other features, which are collectively referred to as Runtime in Webpack. For example, the product typically contains code like this:
/ * * * * * * / (() = > {
// webpackBootstrap
/ * * * * * * / var __webpack_modules__ = {}; // The module cache
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
/ * * * * * * / / * * * * * * / var __webpack_module_cache__ = {}; // The require function
/ * * * * * * /
/ * * * * * * / / * * * * * * / function __webpack_require__(moduleId) {
/ * * * * * * / / * * * * * * / __webpack_modules__[moduleId](
module.module.exports,
__webpack_require__
); // Return the exports of the module
/ * * * * * * /
/ * * * * * * / / * * * * * * / return module.exports;
/ * * * * * * /
} // expose the modules object (__webpack_modules__)
/ * * * * * * /
/ * * * * * * / / * * * * * * / __webpack_require__.m = __webpack_modules__; /* webpack/runtime/compat get default export */
/ * * * * * * /
// ...}) ();Copy the code
At compile time, Webpack outputs runtime code (based on Dependency subclasses) supporting features based on the business code, such as:
- Need to be
__webpack_require__.f
,__webpack_require__.r
And other functions to achieve the minimum modular support - If you use the dynamic loading feature, you need to write
__webpack_require__.e
function - If the Module Federation feature is used, write
__webpack_require__.o
function - , etc.
While each piece of run-time code may be small, the end result can get larger as features increase, especially for multi-entry applications, where packaging a similar run-time code repeatedly at each entry can seem wasteful. For this purpose webPack 5 provides an entry. Runtime configuration item that declares how runtime code is packaged. For usage, simply add the runtime value as a string to the entry, for example:
module.exports = {
entry: {
index: { import: "./src/index".runtime: "solid-runtime"}}};Copy the code
After executing entry and subcontracting asynchronous modules, Webpack iterates through the entry configuration to determine whether it contains the Runtime attribute. If it does, a Chunk named Runtime is created. Therefore, the preceding configuration generates two chunks: Chunk [index.js] and chunk[solid-runtime]. Based on this, two files are generated:
- Entry index corresponds to
index.js
file
- Corresponding to the runtime configuration
solid-runtime.js
file
In a multi-entry scenario, the WebPack runtime code is eventually written to the same chunk as long as the same runtime value is set for each entry, for example for the following configuration:
module.exports = {
entry: {
index: { import: "./src/index".runtime: "solid-runtime" },
home: { import: "./src/home".runtime: "solid-runtime"}}};Copy the code
Index and home share the same Runtime, and three chunks are generated, respectively:
Generate three files simultaneously:
- Entry index corresponds to
index.js
- Entry index corresponds to
home.js
- Corresponding to the runtime code
solid-runtime.js
Problems with subcontracting rules
At this point, the basic logic of Webpack subcontracting rules is introduced. In terms of implementation, most of the functional codes are focused on:
webpack/lib/compilation.js
Of the fileseal
functionwebpack/lib/buildChunkGraph.js
的buildChunkGraph
function
The biggest problem with the default subcontracting rule is that it cannot solve module duplication. If multiple chunks contain the same module, the module will be repeatedly packaged into those chunks without restriction. For example, suppose we have two entries that rely on the same module: main/index:
By default, WebPack does no extra processing for this, but simply packs the C module into both main/index chunks, resulting in:
As you can see, chunks are isolated from each other, and module C is repeatedly packaged, potentially causing unnecessary performance damage to the end product!
To solve this problem, WebPack 3 introduced the CommonChunkPlugin to try to extract common dependencies between entries into separate chunks, but the CommonChunkPlugin is essentially implemented based on a simple chain of parent-child relationships between chunks, It is difficult to infer whether the third extracted package should be the parent chunk or the sub-chunk of the entry. CommonChunkPlugin processes the parent chunk uniformly, which in some cases has a negative impact on performance.
After WebPack 4, a more responsible design was introduced — ChunkGroup was dedicated to relationship chain management, and SplitChunksPlugin was used to implement heuristic subcontract more efficiently and intelligently. This is a very complicated topic that I’m going to break down in the next article. If you are interested, pay attention.
Next day forecast
Later, I will continue to focus on chunk related functions and core implementation principles, including:
- Webpack 4 was introduced later
ChunkGroup
What problems have been solved by the introduction of, and why can the subcontracting function be greatly optimized - Webpack 5 introduced
ChunkGraph
What problem was solved
- What are the capabilities of Chunk, ChunkGroup, and ChunkGraph, how do they cooperate with each other, and why are they split like this
- What subcontracting optimizations does SplitChunksPlugin do, and what plug-in development tips can we learn from them
- What are the best practices for subcontracting from an application, performance perspective
If you are interested, please remember to click “like” and pay attention to it. Your feedback will be a great power for my continuous creation!
Previous articles:
- [10,000 words summary] One article thoroughly understand the core principle of Webpack
- Webpack plug-in architecture in-depth explanation
- 10 minutes introduction to Webpack: module.issuer properties
- Share several Webpack utility analysis tools
- Dependency Graph: Dependency Graph (Dependency Graph