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 processmoduledependencyObjects and their references to each other; On the other hand, various tools and methods are provided to facilitate users to read and take out quicklymoduledependencyAdditional information
  • ModuleGraphConnection: Data structure that records reference relationships between modules and passes internallyoriginModuleProperty to record the parent module in the reference relationshipmoduleProperty logging submodule. In addition, a series of functional tools are provided to determine the validity of the corresponding reference relationship
  • ModuleGraphModuleModuleSupplementary information for objects under Dependency Graph, including module objectsincomingConnections— A ModuleGraphConnection collection pointing to the module itself, that is, who references the module itself;outgoingConnectionsThe 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:

  • ModuleGraphObject through_dependencyMapAttribute recordDependencyObject and theModuleGraphConnectionThe mapping between connection objects can be quickly found based on this mapping in subsequent processingDependencyThe reference corresponding to the instance and the referenced
  • ModuleGraphObject through_moduleMapmoduleOn top ofModuleGraphModuleInformation, andModuleGraphModuleThe most important function is to record the module reference and referenced relationship, subsequent processing can be found based on this attributemoduleAll 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:

  • addDependencyAfter WebPack resolves the reference relationship from the module content, it creates the appropriateDependencySubclass and call the method to recordmoduleThe instance
  • handleModuleCreationAfter the module is parsed, Webpack iterates through the parent module’s dependency collection and calls this method to create itDependencyCorresponding submodule object, which is later calledcompilation.moduleGraph.setResolvedModuleMethod to log the parent-child reference information tomoduleGraphOn 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 dePmoduleThe instance
  • getOutgoingConnections(module: Module): look formoduleAll dependencies of the instance
  • getIssuer(module: Module): look formoduleWhere 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:

  • SplitChunksPluginIn optimization chunks, this is requiredmoduleGraph.getExportsInfoQuery 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.
  • incompilation.sealFunction, you need to traverse entry’s DEP and callmoduleGraph.getModuleGet 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: