找回密码
 立即注册
首页 业界区 业界 Webpack 核心流程

Webpack 核心流程

普料飕 2025-6-6 16:22:58
我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。
本文作者:霜序
三个阶段

初始化阶段


  • 初始化参数:从配置文件、配置对象、shell 参数中读取,与默认的配置参数结合得出最后的参数。
  • 创建编译器对象:通过上一步得到的参数创建 Compiler 对象。
  • 初始化编译器环境:注入内置插件、各种模块工厂、加载配置等。
  • 开始编译:执行 compiler 对象的 run 方法。
  • 确定入口:根据配置中的 entry 找到对应的入口文件,使用compilition.addEntry将入口文件转换为 dependence 对象。
构建阶段


  • 编译模块(make):根据 entry 对应的 dependence 创建 module 对象,调用 loader 将模块转移成为标准的 JS 内容,在将其转成 AST 对象,从中找出该模块依赖的模块,再递归至所有的入口文件都经历了该步骤。
  • 完成模块编译:上一步处理完成之后,得到每一个模块被转译之后的内容以及对应的依赖关系图。
生成阶段


  • 输出资源(seal):根据入口文件和模块之间的依赖关系,组装成一个个包含多个模块的 chunk,再把每一个 chunk 转换成为单独的一个文件放到输出列表,这是最后一次可以修改输出内容的机会。
  • 写入文件系统(emitAssets):确定好了输出内容,根据输出路径和文件名,把文件写入到文件系统。
初始化阶段

1.webp

new webpack(config, callback)

webpack 支持两个参数,config 是 webpack.config.js 中的配置,callback 是回调函数。
webpack 引用于 webpack/lib/webpack.js
2.webp

上图是 webpack() 的流程图,定义了 create 函数
create 函数主要完成

  • 定义相关的参数
  • 通过 createCompiler 创建 compiler 对象
  • 返回 compiler 和其他参数
并会根据 callback 回调执行不同的操作:

  • 如果传入了 callback 参数,会通过 create 方法拿到对应的 compiler 对象,并执行 compiler.run 方法,返回 compiler 对象

    • 在这其中会判断是否配置 watch 参数,如果有会监听文件改变,重新编译

  • 如果没有传入 callback 参数,也会通过 create 方法拿到对应的 compiler 对象,直接返回
因此调用 webpack() 方法有两种方式:
  1. // webpack 函数有传回调函数
  2. const compiler = webpack(config, (err, stats) => {
  3.   if (err) {
  4.     console.log(err)
  5.   }
  6. })
  7. // 执行 webpack 函数没有传回调函数,手动调用一下 compiler.run
  8. const compiler = webpack(config)
  9. compiler.run((err, stats) => {
  10.   if (err) {
  11.     console.log(err)
  12.   }
  13. })
复制代码
createCompiler

3.webp

在上一步中,调用了 create 方法,compiler 对象实则是通过 createCompiler 函数返回的。
主要逻辑都是在 WebpackOptionsApply.process 中,该方法是将 config 中配置的属性转成 plugin 注入到 webpack 中。
通过 Compiler 类创建了 compiler 对象,通过 constructor 初始化一些内容

  • 使用 tapable 初始化一系列的 hooks。
  • 初始化一些参数。
compiler.run

在第一步的时候,调用 webpack 之后,最后都会调用 compiler.run 方法。
从代码中可以看出来,compiler.run 方法主要做了:

  • 定义错误处理函数 finalCallback
  • 定义 onCompiled 作为 this.compile 的回调
  • 定义 run 方法,执行 run 方法
简单来说,compiler.run 其实最后调用的是 compiler.compile 方法
compiler.compile

compiler.compile 该方法中才开始做 make 处理
从代码中可以看出来,compiler.compile 方法主要做了:

  • 初始化 compilation 参数,调用new Compilation创建 compilation 对象
  • 执行 make hook,调用compilation.addEntry方法,进入构建阶段
  • compilation.seal,执行 seal,对 make 阶段处理过的 module 代码进行封装, chunk 输出最终产物
  • afterCompile hook,执行收尾逻辑
调用compile函数触发make钩子后,初始化阶段就算是结束了,流程逻辑开始进入「构建阶段
构建阶段

构建阶段主要使用的 compilation 对象,它和 compiler 是有区别的:
compiler:webpack 刚构建时就会创建 compiler 对象,存在于 webpack 整个生命周期 。
compilation:在准备编译某一个模块的时候才会创建,主要存在于 compile 到 make 这一段生命周期里面。
开启 wacth 对文件进行监听时,文件发生改变需要重新编译时,只需要重新创建一个 compilation 对象即可,不需要重新创建 compiler 对象做很多第一步初始化操作。如果改变了 config,则需要重新执行 dev/build 命令,创建新的 compiler 对象。
4.webp


  • 当执行初始化阶段的时候WebpackOptionsApply.process的时候会去初始化 EntryPlugin 调用compiler.hooks.make.tapAsync注册 compiler 的 make 钩子,用来开启编译。
  • 当初始化完成之后调用compiler.compile方法时,会执行this.hooks.make.callAsync,从而开始执行compilation.addEntry添加入口文件。
  • 调用handleModuleCreation方法,根据文件类型创建不同的 module。
  • 调用module.build开始构建,通过 loader-runner 转译 module 内容,将各种资源转为 webpack 可以理解的 JavaScript 文本。
  • 调用 acorn 的parse方法将 JS 代码解析成为 AST 结构。
  • 通过 JavaScriptParser 类中遍历 AST,触发各种 hooks 。

    • 遇到 import 语句时,触发hooks.exportImportSpecifier。
    • 该 hook 在 HarmonyExportDependencyParserPlugin 插件中被注册,会将依赖资源添加成为 Dependency 对象。
    • 调用module.addDependency将依赖对象加入到 module 依赖列表中。

  • AST 遍历完毕后,调用module.handleParseResult处理模块依赖。
  • 对于 module 新增的依赖,调用handleModuleCreate,控制流回到第一步。
  • 所有依赖都解析完毕后,构建阶段结束。
在整个过程中数据流 module ⇒ AST ⇒ dependency ⇒ module 的转变,将源码转为 AST 主要是为了分析模块的 import 语句收集相关依赖数组,最后遍历 dependences 数组将 Dependency 转换为 Module 对象,之后递归处理这些新的 Module,直到所有项目文件处理完毕。
总结来说就是,从入口文件开始收集其依赖模块,并对依赖模块再进行相同的模块处理。
构建过程
5.webp

例如上图,entry 文件为 index.js,分别依赖 a.js/b.js,其中 a.js 又依赖 c.js/d.js 。
第一步
根据 webpack 初始化之后,能够确定入口文件 index.js,并调用compilation.addEntry函数将之添加为 Module 对象。
6.webp

第二步
通过 acorn 解析 index 文件,分析 AST 得到 index 有两个依赖。
7.webp

第三步
得到了两个 dependence 之后,调用 module[index] 的 handleParserResult 方法处理 a/b 两个依赖对象。
8.webp

第四步
又触发 module[a/b] 的 handleModuleCreation 方法,从 a 模块中又解析到 c/d 两个新依赖,于是再继续调用 module[a] 的 handleParseResult,递归上述流程。
9.webp

第五步
最终得到 a/b/c/d 四个 Module 以及其对应的 dependence。
10.webp

所有的模块构建完毕,没有新的依赖可以继续,由此进入生成阶段。
生成阶段

在构建阶段 make 结束之后,就会进入生成阶段,调用compilation.seal表明正式进入生成阶段。
在 seal 阶段主要是是将构建阶段生成的 module 拆分组合到 chunk 对象中,再转译成为目标环境的产物,并写出为产物文件,解决的是资源输出问题。
11.webp


  • 构建本次编译的ChunkGraph对象
  • 通过hooks.optimizeDependencies优化模块依赖关系
  • 循环compilation.entries入口文件创建 chunks,调用 addChunk 为每一个入口添加 chunk 对象,并且遍历当前入口的 dependency 对象找到对应 module 对象关联到该 chunk
  • 触发optimizeModules/optimizeChunks等钩子,对 chunk 和 module 进行一系列的优化操作,这些优化操作都是有插件去完成的,例如 SplitChunksPlugin
  • 调用codeGeneration方法生成 chunk 代码,会根据不同的 module 类型生成 template 代码
  • 调用createChunkAssets方法为每一个 chunk 生成资产文件
  • compilation.emitAsset 将产物提交到 compilation.assets 中,还尚未写入磁盘
  • 最后执行 callback 回调回到 compile 的控制流中,执行 onCompiled 方法中的 compiler.emitAsset 输出资产文件
流转过程

在 webpack 执行的三个阶段,对应着资源形态扭转,每一个阶段操作的对象都是不一样的
12.webp


  • compication.make

    • 以 entry 文件为入口,作为 dependency 放入 compilcation 的依赖列表
    • 根据 dependences 创建 module 对象,之后读入 module 对应的文件内容,调用 loader-runner 对内容做转化,转化结果若有其它依赖则继续读入依赖资源,重复此过程直到所有依赖均被转化为 module

  • compication.seal

    • 遍历所有的 module,根据 entry 的配置以及 module 的类型,分配到不同的 chunk
    • 将 chunk 构建成为 chunkGraph
    • 遍历 chunkGraph 调用 complication.emitAssets 方法标记 chunk 的输出规则,即转化为 assets 集合。

  • compiler.emitAssets

    • 将 assets 输出到文件系统

总结


  • 初始化阶段:负责构建环境,初始化工厂类,注入内置插件
  • 构建阶段:读入并分析 entry 文件,查找其模块依赖,再一次处理模块依赖的依赖,直到所有的依赖都被处理完毕,该过程解决资源输入问题
  • 生成阶段:根据 entry 的配置将模块封装称为不同的 chunk,经过一系列的优化再将模块代码编译成为最终的形态,按 chunk 合并成最后的产物,该过程解决资源输出问题
最后

欢迎关注【袋鼠云数栈UED团队】~
袋鼠云数栈 UED 团队持续为广大开发者分享技术成果,相继参与开源了欢迎 star

  • 大数据分布式任务调度系统——Taier
  • 轻量级的 Web IDE UI 框架——Molecule
  • 针对大数据领域的 SQL Parser 项目——dt-sql-parser
  • 袋鼠云数栈前端团队代码评审工程实践文档——code-review-practices
  • 一个速度更快、配置更灵活、使用更简单的模块打包器——ko
  • 一个针对 antd 的组件测试工具库——ant-design-testing

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册