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
The Dependency Graph concept from website the Dependency Graph | webpack, original explanation is this:
Any time one file depends on another, webpack treats this as a dependency. This allows webpack to take non-code assets, such as images or web fonts, and also provide them as dependencies for your application.
When webpack processes your application, it starts from a list of modules defined on the command line or in its configuration file. Starting from these entry points, webpack recursively builds a dependency graph that includes every module your application needs, then bundles all of those modules into a small number of bundles – often, just one – to be loaded by the browser.
As WebPack processes the application code, it recursively builds dependency Graph _ containing all modules, starting with entry provided by the developer, and packages these modules as bundles.
Dependency Graph runs through the entire webPack lifecycle, from module parsing in make to chunk generation in SEAL. And tree-shaking capabilities are highly dependent on Dependency Graph, which is a very core data structure built from WebPack resources.
This article will discuss three aspects of [email protected]’s Dependency Graph implementation:
- Dependency Graph is rendered as a data structure in the WebPack implementation
- How to build Dependency Graph by gathering dependencies between modules while Webpack is running
- How will Dependency Graph be consumed once it’s built
Learning this article, you will further understand the processing details of webPack module parsing, combined with the previous article [ten thousand word summary] to understand the core principle of Webpack, you can have a more thorough understanding of the core mechanism of Webpack.
Dependency Graph
This section delves into the WebPack source code to look at Dependency Graph’s internal data structure and Dependency collection process. Before we roll it out, it’s worth reviewing a few important WebPack concepts:
Module
: A resource mapping object within webpack, containing information about the resource’s path, context, dependencies, content, and so on
Dependency
: References other modules in a module, for exampleimport "a.js"
Statement, WebPack will first express the reference relationship as a Dependency subclass and associate it with a Module object. After the contents of the current Module have been resolved, the next loop will start converting the Dependency object to the appropriate Module subclass.Chunk
: After analyzing the contents of all module resources and building a complete Dependency Graph, WebPack will build one or more chunk instances according to the user configuration and Dependency Graph content. Each chunk corresponds roughly to the final output file.
The data structure
The Dependency Graph of Webpack 4.x is relatively simple. It mainly records the referenced and referenced relationships by the Dependence/Module built-in properties.
However, after Webpack 5.0, a relatively complex class structure is implemented to record the dependency between modules, and the dependency related logic is decouple from Dependence/Module to an independent type structure. The main types are as follows:
ModuleGraph
: a container for logging Dependency Graph information, which on the one hand holds everything involved in the build processmodule
、dependency
Objects and their references to each other; On the other hand, various tools and methods are provided to facilitate users to read and take out quicklymodule
或dependency
Additional information
ModuleGraphConnection
: Data structure that records reference relationships between modules and passes internallyoriginModule
Property to record the parent module in the reference relationshipmodule
Property logging submodule. In addition, a series of functional tools are provided to determine the validity of the corresponding reference relationshipModuleGraphModule
:Module
Supplementary information for objects under Dependency Graph, including module objectsincomingConnections
— A ModuleGraphConnection collection pointing to the module itself, that is, who references the module itself;outgoingConnections
The external dependencies of the module, that is, which modules the module refers to.
The relationship between classes is roughly as follows:
The class diagram above requires additional attention:
ModuleGraph
Object through_dependencyMap
Attribute recordDependency
Object and theModuleGraphConnection
The mapping between connection objects can be quickly found based on this mapping in subsequent processingDependency
The reference corresponding to the instance and the referenced
ModuleGraph
Object through_moduleMap
在module
On top ofModuleGraphModule
Information, andModuleGraphModule
The most important function is to record the module reference and referenced relationship, subsequent processing can be found based on this attributemodule
All dependencies and dependencies of the instance
Dependency collection process
ModuleGraph, ModuleGraphConnection, and ModuleGraphModule work together to gradually collect dependencies between modules during the WebPack build process (make phase). Review the construction flow chart of the core principles of Webpack mentioned in the previous article:
The construction process itself is very complicated. It is recommended that readers understand the core principles of Webpack by comparing the article [10,000-word Summary]. The dependency collection process takes place at two nodes:
addDependency
After WebPack resolves the reference relationship from the module content, it creates the appropriateDependency
Subclass and call the method to recordmodule
The instance
handleModuleCreation
After the module is parsed, Webpack iterates through the parent module’s dependency collection and calls this method to create itDependency
Corresponding submodule object, which is later calledcompilation.moduleGraph.setResolvedModule
Method to log the parent-child reference information tomoduleGraph
On the object
The logic of the setResolvedModule method is roughly as follows:
class ModuleGraph {
constructor() {
/ * *@type {Map<Dependency, ModuleGraphConnection>} * /
this._dependencyMap = new Map(a);/ * *@type {Map<Module, ModuleGraphModule>} * /
this._moduleMap = new Map(a); }/ * * *@param {Module} originModule the referencing module
* @param {Dependency} dependency the referencing dependency
* @param {Module} module the referenced module
* @returns {void}* /
setResolvedModule(originModule, dependency, module) {
const connection = new ModuleGraphConnection(
originModule,
dependency,
module.undefined,
dependency.weak,
dependency.getCondition(this));this._dependencyMap.set(dependency, connection);
const connections = this._getModuleGraphModule(module).incomingConnections;
connections.add(connection);
const mgm = this._getModuleGraphModule(originModule);
if (mgm.outgoingConnections === undefined) {
mgm.outgoingConnections = new Set();
}
mgm.outgoingConnections.add(connection);
}
}
Copy the code
The code above mainly changes the _dependencyMap and moduleGraphModule inbound and outbound connections properties to collect upstream and downstream dependencies for the current module.
Instance analysis
Look at a simple example for the following dependencies:
Webpack starts, during the construction phase recursive call compilation. HandleModuleCreation function, gradually filling the Dependency Graph structure, could eventually generate the following results:
ModuleGraph: {
_dependencyMap: Map(3){
{
EntryDependency{request: "./src/index.js"} => ModuleGraphConnection{
module: NormalModule{request: "./src/index.js"},
// The entry module has no references, so set it to null
originModule: null
}
},
{
HarmonyImportSideEffectDependency{request: "./src/a.js"} => ModuleGraphConnection{
module: NormalModule{request: "./src/a.js"},
originModule: NormalModule{request: "./src/index.js"}
}
},
{
HarmonyImportSideEffectDependency{request: "./src/a.js"} => ModuleGraphConnection{
module: NormalModule{request: "./src/b.js"},
originModule: NormalModule{request: "./src/index.js"}}}},_moduleMap: Map(3){
NormalModule{request: "./src/index.js"} => ModuleGraphModule{
incomingConnections: Set(1) [
// Entry module, corresponding to originModule null
ModuleGraphConnection{ module: NormalModule{request: "./src/index.js"}, originModule:null}].outgoingConnections: Set(2) [
// From index to module A
ModuleGraphConnection{ module: NormalModule{request: "./src/a.js"}, originModule: NormalModule{request: "./src/index.js"}},// From index to module B
ModuleGraphConnection{ module: NormalModule{request: "./src/b.js"}, originModule: NormalModule{request: "./src/index.js"} }
]
},
NormalModule{request: "./src/a.js"} => ModuleGraphModule{
incomingConnections: Set(1) [
ModuleGraphConnection{ module: NormalModule{request: "./src/a.js"}, originModule: NormalModule{request: "./src/index.js"}}].// Module A has no other dependencies, so the outgoingConnections attribute is undefined
outgoingConnections: undefined
},
NormalModule{request: "./src/b.js"} => ModuleGraphModule{
incomingConnections: Set(1) [
ModuleGraphConnection{ module: NormalModule{request: "./src/b.js"}, originModule: NormalModule{request: "./src/index.js"}}].// the b module has no other dependencies, so the outgoingConnections property is undefined
outgoingConnections: undefined}}}Copy the code
As can be seen from Dependency Graph above, modulegraph. _moduleMap has essentially formed a directed acyclic Graph structure, where the key of dictionary _moduleMap is the node of the Graph. If the outgoingConnections attribute in the Value ModuleGraphModule structure is an edge of the graph, all vertices of the graph can be traversed from the starting point index.js along the outgoingConnections in the above example.
role
In the case of [email protected], the moduleGraph keyword appears 1277 times, covering almost all files in the Webpack /lib folder. Although the frequency of occurrence is high, in general you can see that there are two main functions: information indexing and conversion to a ChunkGraph to determine the output structure.
Information indexes
The ModuleGraph type provides a number of utility functions for querying module/Dependency information, such as:
getModule(dep: Dependency)
: Searches for the corresponding object according to dePmodule
The instance
getOutgoingConnections(module: Module)
: look formodule
All dependencies of the instancegetIssuer(module: Module)
: look formodule
Where to get referenced (For more information on the issuer mechanism, see my other article:10 minutes introduction to Webpack: module.issuer properties )
And so on.
Many plug-ins, Dependency subclasses, and Module subclasses within [email protected] use these utility functions to find information about specific modules and dependencies, such as:
SplitChunksPlugin
In optimization chunks, this is requiredmoduleGraph.getExportsInfo
Query the value of each moduleexportsInfo
(The set of information exported by the module, related to tree-shaking, will be covered in a separate article.) Information to determine how to separatechunk
.
- in
compilation.seal
Function, you need to traverse entry’s DEP and callmoduleGraph.getModule
Get the complete Module definition - .
So, when you write a plugin, may consider appropriate reference webpack/lib/ModuleGraph provides methods in js, confirm access to use the function to get the information you need.
Build ChunkGraph
In the Webpack body process, after the make build phase, the seal phase begins to comb out how the output is organized. At [email protected], seal mainly focused on Chunk and ChunkGroup, but after 5.0, Similar to Dependency Graph, a new chunkGraph-based Graph structure was introduced to implement resource generation algorithms.
In the compilation. The seal function, first of all, according to the rules of the default – each entry corresponding group as a chunk, called after webpack/lib/buildChunkGraph js file defines buildChunkGraph method, Convert module dependencies to chunkGraph objects by iterating through the moduleGraph object generated in the make phase.
This logic is particularly complex, is not here, next time will separate out an article on the chunk/chunkGroup/chunkGraph object rules built into the module output.
conclusion
The Dependency Graph concept discussed in this article is widely used within WebPack, so understanding it can be a great help in understanding the Source code for WebPack, or in learning how to write plug-ins or loaders. In fact, many new knowledge blind spots have been discovered in the analysis process:
- What is the full mechanics of Chunk?
- How is the complete system of Dependency implemented and what is its effect?
- How to collect Module exportsInfo? How is shaking used in tree-shaking?
If you are also interested in the above questions, please feel free to give a thumbs-up, and there will be more useful articles about Webpack in the future.
Previous articles:
- [10,000 words summary] One article thoroughly understand the core principle of Webpack
- 10 minutes introduction to Webpack: module.issuer properties
- Webpack plug-in architecture in-depth explanation
Continue to output in-depth front-end technology articles, follow the wechat public account: