2024-07-08
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
In addition to the main process, we will also explain several vague concepts in detail:
in front Init、Make、SealIn the previous section, we have introduced that Webpack's underlying construction logic can be roughly divided into:Initialization, construction, packaging"three phases:
in,"ConstructThe "phase is responsible for analyzing the dependencies between modules and establishing Dependency graph(ModuleGraph); Next, in "Encapsulation"In this stage, according to the dependency graph, the modules are encapsulated separately into several Chunk objects, and the parent-child dependency relationships between Chunks are sorted into ChunkGraph and several ChunkGroup objects.
The most important goal of the "encapsulation" phase is to build a ChunkGraph relationship graph based on the ModuleGraph relationship graph collected in the "construction" phase. The logic of this process is relatively complex:
Let's briefly analyze the implementation logic of several important steps here.
The first step is critical: transferseal()
After the function, traverseentry
Configuration, creating an emptyChunk
andEntryPoint Object (a specialChunkGroup
), and initially set up the basic ChunkGraph
Structural relationship, ready for the next step, key code:
class Compilation {
seal(callback) {
// ...
const chunkGraphInit = new Map();
// 遍历入口模块列表
for (const [name, { dependencies, includeDependencies, options }] of this
.entries) {
// 为每一个 entry 创建对应的 Chunk 对象
const chunk = this.addChunk(name);
// 为每一个 entry 创建对应的 ChunkGroup 对象
const entrypoint = new Entrypoint(options);
// 关联 Chunk 与 ChunkGroup
connectChunkGroupAndChunk(entrypoint, chunk);
// 遍历 entry Dependency 列表
for (const dep of [...this.globalEntry.dependencies, ...dependencies]) {
// 为每一个 EntryPoint 关联入口依赖对象,以便下一步从入口依赖开始遍历其它模块
entrypoint.addOrigin(null, { name }, /** @type {any} */ (dep).request);
const module = this.moduleGraph.getModule(dep);
if (module) {
// 在 ChunkGraph 中记录入口模块与 Chunk 关系
chunkGraph.connectChunkAndEntryModule(chunk, module, entrypoint);
// ...
}
}
}
// 调用 buildChunkGraph 方法,开始构建 ChunkGraph
buildChunkGraph(this, chunkGraphInit);
// 触发各种优化钩子
// ...
}
}
After execution is completed, the following data structure is formed:
Secondly, if you configure entry.runtime
Webpack also adds runtime code at this stage create The corresponding Chunk and directlydistribute Giveentry
correspondingChunkGroup
Object. Called when everything is ready buildChunkGraph function and proceed to the next step.
Step 2: existbuildChunkGraph
Inside a functiontransfer visitModules
Function, traverse ModuleGraph, assign all Modules to differentChunk
object; if you encounterAsynchronous Module, then this module createnew ChunkGroup
andChunk
Object, and finally form the following data structure:
third step: existbuildChunkGraph
In the functiontransfer connectChunkGroups
Method, establishChunkGroup
between,Chunk
The dependencies between them generate a completeChunkGraph
Object, and finally form the following data structure:
the fourth step: existbuildChunkGraph
In the functiontransfer cleanupUnconnectedGroups
Method, cleanup is invalidChunkGroup
, which mainly plays a role in performance optimization.
After these four steps from top to bottom,ModuleGraph
The modules stored in the will be assigned to three different Chunk objects: Entry, Async, and Runtime according to the nature of the modules themselves, and the dependencies between Chunks are stored in the ChunkGraph and ChunkGroup collections. The subpackaging strategy can be modified based on these objects later (for exampleSplitChunksPlugin
), by reorganizing and allocating the ownership of Module and Chunk objects to achieve subpackaging optimization.
The above construction process involves three key objects: Chunk, ChunkGroup, and ChunkGraph. Let's first summarize their concepts and functions to deepen our understanding:
Chunk
:Module is used to read module content, record dependencies between modules, etc.; while Chunk merges multiple modules according to module dependencies and outputs them into asset files (the logic of merging and outputting products will be explained in the next chapter):ChunkGroup
:one ChunkGroup
Contains one or moreChunk
Object;ChunkGroup
andChunkGroup
A parent-child dependency relationship is formed between them:ChunkGraph
: Finally, Webpack will store the dependencies between Chunks and ChunkGroups in compilation.chunkGraph
In the object, the following type of relationship is formed:The above ChunkGraph
The build process will eventually organize the Module into three different types of Chunks:
entry
The modules reached below are organized into a Chunk;entry.runtime
When it is not empty, the runtime module will be organized into a separate Chunk.This is built-in to Webpack. splitChunks
Or other plug-ins, the default rules for mapping module input to output are one of the key underlying principles of Webpack, so it is necessary to introduce the specific rules of each Chunk.
Entry Chunk:
Starting with Entry Chunk, Webpack will first create a new entry chunk for each entry. entry
createChunk
For example, for the following configuration:
module.exports = {
entry: {
main: "./src/main",
home: "./src/home",
}
};
Traversal entry
Object properties and createchunk[main]
、chunk[home]
Two objects, the two Chunks containmain
、home
Modules:
After initialization, Webpack will ModuleGraph
The dependency data willentry
All modules touched by the next step are stuffed into the Chunk (occurs invisitModules Method), for example, for the following file dependencies:
main.js
The four files a/b/c/d are directly or indirectly referenced in a synchronous manner. Webpack will firstmain.js
The module creates Chunk and EntryPoint objects, and then gradually adds modules a/b/c/d tochunk[main]
In the end, it forms:
Async Chunk:
Second, Webpack will convert each asynchronous import statement (import(xxx)
andrequire.ensure
) is processed into a separate Chunk object, and all its submodules are added to this Chunk - we call it Async Chunk. For example, for the following example:
// index.js
import './sync-a.js'
import './sync-b.js'
import('./async-a.js')
// async-a.js
import './sync-c.js'
In the entry module index.js
In the sync-a and sync-b modules, import them synchronously; import the async-a module asynchronously; and import them synchronously in async-a.sync-c
Modules form the following module dependency diagram:
At this point, Webpack will be the entry index.js
, asynchronous module async-a.js
Create sub-packages separately to form the following Chunk structure:
and chunk[index]
andchunk[async-a]
A one-way dependency is formed between them, and Webpack will save this dependency inChunkGroup._parents
、ChunkGroup._children
Properties.
Runtime Chunk:
Finally, except entry
In addition to asynchronous modules, Webpack5 also supports extracting runtime code into chunks. The runtime code here refers to a series of basic framework codes injected by Webpack to ensure that the packaged product can run normally. For example, the common Webpack packaged product structure is as follows:
The large section of code circled in red in the figure above is the runtime code dynamically generated by Webpack. When compiling, Webpack will decide which runtime code to output based on the business code (based onDependency
subclasses), for example:
__webpack_require__.f
、__webpack_require__.r
The functions such as the above provide the minimum modular support;__webpack_require__.e
function;__webpack_require__.o
function;Although each runtime code may be small, as features are added, the final result will become larger and larger. Especially for multi-entry applications, it is a bit wasteful to repeatedly package a similar runtime at each entry. For this reason, Webpack 5 provides entry.runtime
Configuration items are used to declare how to package runtime code.entry
Add a string to the itemruntime
Values, for example:
module.exports = {
entry: {
index: { import: "./src/index", runtime: "solid-runtime" },
}
};
exist compilation.seal
function, Webpack firstentry
createEntryPoint
, then judge entry
Is there a configurationruntime
Attributes, if any, are createdruntime
The value is the named Chunk, so the above configuration will generate two Chunks:chunk[index.js]
、chunk[solid-runtime]
, and finally produce two files:
index.js
document;solid-runtime.js
document.In more entry
In the scene, as long as eachentry
All set the sameruntime
value, the Webpack runtime code will be merged and written into the same Runtime Chunk, ultimately achieving product performance optimization. For example, for the following configuration:
module.exports = {
entry: {
index: { import: "./src/index", runtime: "solid-runtime" },
home: { import: "./src/home", runtime: "solid-runtime" },
}
};
Entrance index
、home
Share the sameruntime
Value, and finally generate three Chunks, namely:
Entrance at this time chunk[index]
、chunk[home]
With runtimechunk[solid-runtime]
A parent-child dependency relationship will also be formed.
The biggest problem with the default subpackaging rule is that it cannot solve module duplication. If multiple chunks contain the same module at the same time, the module will be repeatedly packaged into these chunks without restriction. For example, suppose we have two entry points main/index that depend on the same module at the same time:
By default, Webpack will not do any additional processing for this, but simply pack the c module into two chunks, main/index, at the same time, and finally form:
can be seen chunk
The modules are isolated from each other, and module c is packaged repeatedly, which may cause unnecessary performance loss to the final product!
To solve this problem, Webpack 3 introduced CommonChunkPlugin
The plugin attempts to extract common dependencies between entries into separatechunk
,but CommonChunkPlugin
Essentially, it is still based on a simple parent-child relationship chain between Chunks. It is difficult to infer that the third package extracted should be used asentry
Fatherchunk
Still a childchunk
,CommonChunkPlugin
Unified treatment for the parentchunk
, which in some cases has a significant negative impact on performance.
For this reason, a more complex data structure was introduced after Webpack4. ChunkGroup
Specialized in relationship chain management,SplitChunksPlugin
Able to achieve more efficient and intelligentHeuristic subpackaging.
In summary, the "Build" phase is responsible for building a ModuleGraph based on the reference relationship of the modules; the "Encapsulation" phase is responsible for building a series of Chunk objects based on the ModuleGraph, and organizing the dependencies between Chunks (asynchronous references, Runtime) into a ChunkGraph - a Chunk dependency graph object. Similar to the ModuleGraph, the introduction of the ChunkGraph structure can also decouple the management logic of the dependencies between Chunks, making the overall architecture logic more reasonable and easier to expand.
However, although it looks complicated, the most important goal of the "packaging" stage is to determine how many chunks there are and which modules are contained in each chunk - these are the key factors that truly affect the final packaging results.
To this end, we need to understand the three built-in subpackaging rules of Webpack5: Entry Chunk, Async Chunk and Runtime Chunk. These are the most primitive subpackaging logics. Other plugins (such as splitChunksPlugin) are all based on this, with the help of buildChunkGraph
The various hooks triggered later further split, merge, and optimize the Chunk structure to achieve the extended subpackaging effect.
think Chunk
Will there be only one product file produced? Why?mini-css-extract-plugin
、file-loader
How is this type of component that can write additional files implemented at the underlying level?