关于webpack:Webpack-系列第三篇Dependency-Graph-深度解析

6次阅读

共计 7052 个字符,预计需要花费 18 分钟才能阅读完成。

全文 2500 字,浏览时长约 30 分钟。如果感觉文章有用,欢送点赞关注,但写作实属不易,未经作者批准,禁止任何模式转载!!!

背景

Dependency Graph 概念来自官网 Dependency Graph | webpack 一文,原文解释是这样的:

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.

翻译过去外围意思是:webpack 解决利用代码时,会从开发者提供的 entry 开始递归地组建起蕴含所有模块的 dependency graph _,_之后再将这些 module 打包为 bundles。

然而事实远不止官网形容的这么简略,Dependency Graph 贯通 webpack 整个运行周期,从 make 阶段的模块解析,到 seal 阶段的 chunk 生成,以及 tree-shaking 性能都高度依赖于 Dependency Graph,是 webpack 资源构建的一个十分外围的数据结构。

本文将围绕 webpack\@v5.x 的 Dependency Graph 实现,展开讨论三个方面的内容:

  • Dependency Graph 在 webpack 实现中以何种数据结构出现
  • Webpack 运行过程中如何收集模块间依赖关系,进而构建出 Dependency Graph
  • Dependency Graph 构建结束后,又是如何被生产的

学习本文,您将进一步理解 webpack 模块解析的解决细节,联合前文 [万字总结] 一文吃透 Webpack 外围原理,您能够更透彻地理解 webpack 的外围机制。

关注公众号【Tecvan】,回复【1】,获取 Webpack 常识体系脑图

Dependency Graph

本节将深刻 webpack 源码,解读 Dependency Graph 的外在数据结构及依赖关系收集过程。在正式开展之前,有必要回顾几个 webpack 重要的概念:

  • Module:资源在 webpack 外部的映射对象,蕴含了资源的门路、上下文、依赖、内容等信息
  • Dependency:在模块中援用其它模块,例如 import "a.js" 语句,webpack 会先将援用关系表述为 Dependency 子类并关联 module 对象,等到以后 module 内容都解析结束之后,启动下次循环开始将 Dependency 对象转换为适当的 Module 子类。
  • Chunk:用于组织输入构造的对象,webpack 剖析完所有模块资源的内容,构建出残缺的 Dependency Graph 之后,会依据用户配置及 Dependency Graph 内容构建出一个或多个 chunk 实例,每个 chunk 与最终输入的文件大抵上是一一对应的。

数据结构

Webpack 4.x 的 Dependency Graph 实现较简略,次要由 Dependence/Module 内置的系列属性记录援用、被援用关系。

而 Webpack 5.0 之后则实现了一套绝对简单的类构造记录模块间依赖关系,将模块依赖相干的逻辑从 Dependence/Module 解耦为一套独立的类型构造,次要类型有:

  • ModuleGraph:记录 Dependency Graph 信息的容器,一方面保留了构建过程中波及到的所有 moduledependency 对象,以及这些对象相互之间的援用;另一方面提供了各种工具办法,不便使用者迅速读取出 moduledependency 附加的信息
  • ModuleGraphConnection:记录模块间援用关系的数据结构,外部通过 originModule 属性记录援用关系中的父模块,通过 module 属性记录子模块。此外还提供了一系列函数工具用于判断对应的援用关系的有效性
  • ModuleGraphModuleModule 对象在 Dependency Graph 体系下的补充信息,蕴含模块对象的 incomingConnections —— 指向模块自身的 ModuleGraphConnection 汇合,即谁援用了模块本人;outgoingConnections —— 该模块对外的依赖,即该模块援用了其余那些模块。

类间关系大抵为:

下面类图须要额定留神:

  • ModuleGraph 对象通过 _dependencyMap 属性记录 Dependency 对象与 ModuleGraphConnection 连贯对象之间的映射关系,后续的解决中能够基于这层映射迅速找到 Dependency 实例对应的援用与被援用者
  • ModuleGraph 对象通过 _moduleMapmodule 根底上附加 ModuleGraphModule 信息,而 ModuleGraphModule 最大的作用就是记录了模块的援用与被援用关系,后续的解决能够基于该属性找到 module 实例的所有依赖与被依赖关系

依赖收集过程

ModuleGraphModuleGraphConnectionModuleGraphModule 三者合作,在 webpack 构建过程 (make 阶段) 中逐渐收集模块间的依赖关系,回顾前文 [万字总结] 一文吃透 Webpack 外围原理 提及的构建流程图:

构建流程自身很简单,倡议读者比照浏览 [万字总结] 一文吃透 Webpack 外围原理 一文,加深了解。依赖关系收集过程次要产生在两个节点:

  • addDependency:webpack 从模块内容中解析出援用关系后,创立适当的 Dependency 子类并调用该办法记录到 module 实例
  • handleModuleCreation:模块解析结束后,webpack 遍历父模块的依赖汇合,调用该办法创立 Dependency 对应的子模块对象,之后调用 compilation.moduleGraph.setResolvedModule 办法将父子援用信息记录到 moduleGraph 对象上

setResolvedModule 办法的逻辑大抵为:

class ModuleGraph {constructor() {/** @type {Map<Dependency, ModuleGraphConnection>} */
        this._dependencyMap = new Map();
        /** @type {Map<Module, ModuleGraphModule>} */
        this._moduleMap = new Map();}

    /**
     * @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);
    }
}

上例代码次要更改了 _dependencyMapmoduleGraphModule 的出入 connections 属性,以此收集以后模块的上下游依赖关系。

实例解析

看个简略例子,对于上面的依赖关系:

Webpack 启动后,在构建阶段递归调用 compilation.handleModuleCreation 函数,逐渐补齐 Dependency Graph 构造,最终可能生成如下数据后果:


ModuleGraph: {_dependencyMap: Map(3){
        {EntryDependency{request: "./src/index.js"} => ModuleGraphConnection{module: NormalModule{request: "./src/index.js"}, 
                // 入口模块没有援用者,故设置为 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 模块,对应 originModule 为 null
                ModuleGraphConnection{module: NormalModule{request: "./src/index.js"}, originModule:null }
            ],
            outgoingConnections: Set(2) [
                // 从 index 指向 a 模块
                ModuleGraphConnection{module: NormalModule{request: "./src/a.js"}, originModule: NormalModule{request: "./src/index.js"} },
                // 从 index 指向 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"} }
            ],
            // a 模块没有其余依赖,故 outgoingConnections 属性值为 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"} }
            ],
            // b 模块没有其余依赖,故 outgoingConnections 属性值为 undefined
            outgoingConnections: undefined
        }
    }
}

从下面的 Dependency Graph 能够看出,实质上 ModuleGraph._moduleMap 曾经造成了一个有向无环图构造,其中字典 _moduleMap 的 key 为图的节点,对应 value ModuleGraphModule 构造中的 outgoingConnections 属性为图的边,则上例中从终点 index.js 登程沿 outgoingConnections 向前可遍历出图的所有顶点。

作用

以 webpack\@v5.16.0 为例,关键字 moduleGraph 呈现了 1277 次,简直笼罩了 webpack/lib 文件夹下的所有文件,其作用可见一斑。尽管呈现的频率很高,但总的来说能够看出有两个次要作用:信息索引、转变为 ChunkGraph 以确定输入构造。

信息索引

ModuleGraph 类型提供了很多实现 module / dependency 信息查问的工具函数,例如:

  • getModule(dep: Dependency):依据 dep 查找对应的 module 实例
  • getOutgoingConnections(module: Module):查找 module 实例的所有依赖
  • getIssuer(module: Module):查找 module 在何处被援用(对于 issuer 机制的更多信息,可参考我的另一篇文章:十分钟精进 Webpack:module.issuer 属性详解)

等等。

Webpack\@v5.x 外部的许多插件、Dependency 子类、Module 子类的实现都须要用到这些工具函数查找特定模块、依赖的信息,例如:

  • SplitChunksPlugin 在优化 chunks 解决中,须要应用 moduleGraph.getExportsInfo 查问各个模块的 exportsInfo (模块导出的信息汇合,与 tree-shaking 强相干,后续会单出一篇文章解说)信息以确定如何拆散 chunk
  • compilation.seal 函数中,须要遍历 entry 对应的 dep 并调用 moduleGraph.getModule 获取残缺的 module 定义

那么,在您编写插件时,能够思考适度参考 webpack/lib/ModuleGraph.js 中提供的办法,确认能够获取应用那些函数获取到您所须要的信息。

构建 ChunkGraph

Webpack 主体流程中,make 构建阶段完结之后会进入 seal 阶段,开始梳理以何种形式组织输入内容。在 webpack\@v4.x 时,seal 阶段次要围绕 ChunkChunkGroup 两个类型开展,而到了 5.0 之后,与 Dependency Graph 相似也引入了一套全新的基于 ChunkGraph 的图构造实现资源生成算法。

在 compilation.seal 函数中,首先依据默认规定 —— 每个 entry 对应组织为一个 chunk,之后调用 webpack/lib/buildChunkGraph.js 文件定义的 buildChunkGraph 办法,遍历 make 阶段生成的 moduleGraph 对象从而将 module 依赖关系转化为 chunkGraph 对象。

这一块的逻辑也特地简单,不在这里开展,下次会独自出一篇文章解说 chunk/chunkGroup/chunkGraph 等对象构筑成的模块输入规定。

总结

本文探讨的 Dependency Graph 概念在 webpack 外部被大量应用,因而了解这个概念对咱们了解 webpack 源码,或者学习如何编写插件、loader 都会有极大的帮忙。在剖析过程其实也挖掘出了很多新的常识盲点:

  • Chunk 的残缺机制是怎么样的?
  • Dependency 的残缺体系是如何实现的,有何作用?
  • Module 的 exportsInfo 如何收集?在 tree-shaking 过程中如何被应用?

如果你也对上述问题感兴趣,欢送点赞关注,后续会围绕 webpack 输入更多有用的文章。

往期文章:

  • [万字总结] 一文吃透 Webpack 外围原理
  • 十分钟精进 Webpack:module.issuer 属性详解
  • [源码解读] Webpack 插件架构深度解说
正文完
 0