webpack模块化原理

726 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

webpack模块化原理

本文我们来探讨下webpack模块化原理,从webpack工作流程开始,到实现模块化。

webpack 工作流程是怎样的

webpack 是一种模块打包工具,可以将各类型的资源,例如图片、CSS、JS 等,转译组合为 JS 格式的 bundle 文件。

webpack 构建的核心任务是完成内容转化和资源合并。主要包含以下 3 个阶段:

  1. 初始化阶段

    • 初始化参数:从配置文件、配置对象和 Shell 参数中读取并与默认参数进行合并,组合成最终使用的参数。
    • 创建编译对象:用上一步得到的参数创建 Compiler 对象。
    • 初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化 RuleSet 集合、加载配置的插件等。
  2. 构建阶段

    • 开始编译:执行 Compiler 对象的 run 方法,创建 Compilation 对象。
    • 确认编译入口:进入 entryOption 阶段,读取配置的 Entries,递归遍历所有的入口文件,调用 Compilation.addEntry 将入口文件转换为 Dependency 对象。
    • 编译模块(make) : 调用 normalModule 中的 build 开启构建,从 entry 文件开始,调用 loader 对模块进行转译处理,然后调用 JS 解释器(acorn)将内容转化为 AST 对象,然后递归分析依赖,依次处理全部文件。
    • 完成模块编译:在上一步处理好所有模块后,得到模块编译产物和依赖关系图。
  3. 生成阶段

    • 输出资源(seal) :根据入口和模块之间的依赖关系,组装成多个包含多个模块的 Chunk,再把每个 Chunk 转换成一个 Asset 加入到输出列表,这步是可以修改输出内容的最后机会。
    • 写入文件系统(emitAssets) :确定好输出内容后,根据配置的 output 将内容写入文件系统。

使用模块化加载

模块化简单来说就是是指把一个复杂的系统分解到多个模块以方便编码。JS模块化的大致流程为:CommonJS(服务端) -> AMD(浏览器端)-> UMD(兼容了CommonJS和AMD) -> ES Module(ES6标准)。

接下来我们看看大项目中,各种模块化是怎么被webpack加工的。

CJS 模块化实现原理

CommonJS,require()全局方法同步加载模块,开发基于nodejs的服务端应用时采用规范。

CJS同步加载模块在客户端(浏览器)加载模块会阻塞后续代码的执行,因为客户端相较服务端性能更差,所以同步加载模块将浪费大量时间,因此想到了异步加载,也就是AMD主要解决的问题

ESM 实现原理

当你在开发这些模块时,你建立了一个图。

浏览器或者 Node 是通过这些引入声明,才明确的知道你需要加载哪些代码。你需要创建一个文件作为这个依赖关系的入口。之后就会根据那些 import 声明去查找剩余的代码。

但是这些文件不能直接被浏览器所用,这些文件会被解析成叫做模块记录的数据结构。之后,这个模块记录将会被转变成一个模块实例。一个模块实例是由两部分组成:代码和状态

代码是这一列指令的基础。它就像该如何去做的引导。但是只凭它你并不能做什么。你需要材料才能够去使用这些引导。

什么是状态?状态给你提供了材料!在任何时候,状态都会为你提供这些变量真实的值。当然这些变量都仅仅只是作为内存中存储这些值的别名而已(引用)。

模块实例将代码(一系列的引导)和状态组合起来(所有变量在内存中的值)。

我们需要的是每个模块拥有自己的模块实例。模块的加载过程是通过入口文件,找到整个模块实例的关系表。

  1. 对于 ES modules 来说,这个过程需要三步:

    • 构建——查找、下载以及将所有文件解析进入模块记录。
    • 实例化——查找暴露出的值应该放在内存中的哪个位置(但是不会给它们填充值),然后在内存中创建 exports 和 imports 应该存在的地方。这被称作链接。
    • 求值——运行代码,把内存中的变量赋予真实的值。

人们都说 ES modules 是异步的。你完全可以将它想成异步的,因为整个流程被分成三个不同的阶段——加载,实例化以及求值——还有,这些步骤都是被分开执行的。

这就意味着,这个规则是一种异步的而且不从属于 CommonJS。我将在稍后解释它,在 CommonJS 中,一个模块的依赖是在模块加载之后才立刻进行加载、实例化、求值的,中间不会有任何的打断(也就是同步)。

无论如何,这些步骤本身并不一定是异步的。它们可以被同步处理。这就依赖于加载的过程取决于什么?那是因为并不是所有的东西都尊崇于 ES modules 规范。这其实是两部分工作,从属于不同的规范。

ES module 规范阐述了你应该如何将这些文件解析成模块记录,以及你应该如何去实例化和进行求值。但是,它没有说明如何去首先获得这些文件。

获取这些文件有相应的加载器,在不同的说明中,加载器都被明确定义了。对于浏览器,它的规范是HTML spec。但是你可以在不同平台使用不同的加载器。

加载器同样明确指出了控制模块应该如何被加载。这被称作 ES 模块方法 —— ParseModule,Module.Instantiate,以及Module.Evaluate。这就像JS 引擎操纵的木偶一样。



小结

最后,让我们一起加油吧!

gg.jpg

都看到这了,不如顺手点个赞再走 ( *ˇωˇ* )