我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。
本文作者:霜序
compilition.addEntry
将入口文件转换为 dependence 对象。webpack 支持两个参数,config 是 webpack.config.js 中的配置,callback 是回调函数。
webpack 引用于 webpack/lib/webpack.js
上图是 webpack() 的流程图,定义了 create 函数
create 函数主要完成
并会根据 callback 回调执行不同的操作:
因此调用 webpack() 方法有两种方式:
// webpack 函数有传回调函数
const compiler = webpack(config, (err, stats) => {
if (err) {
console.log(err)
}
})
// 执行 webpack 函数没有传回调函数,手动调用一下 compiler.run
const compiler = webpack(config)
compiler.run((err, stats) => {
if (err) {
console.log(err)
}
})
在上一步中,调用了 create 方法,compiler 对象实则是通过 createCompiler 函数返回的。
主要逻辑都是在 WebpackOptionsApply.process 中,该方法是将 config 中配置的属性转成 plugin 注入到 webpack 中。
通过 Compiler 类创建了 compiler 对象,通过 constructor 初始化一些内容
在第一步的时候,调用 webpack 之后,最后都会调用 compiler.run 方法。
从代码中可以看出来,compiler.run 方法主要做了:
简单来说,compiler.run 其实最后调用的是 compiler.compile 方法
compiler.compile 该方法中才开始做 make 处理
从代码中可以看出来,compiler.compile 方法主要做了:
new Compilation
创建 compilation 对象compilation.addEntry
方法,进入构建阶段调用compile
函数触发make
钩子后,初始化阶段就算是结束了,流程逻辑开始进入「构建阶段」
构建阶段主要使用的 compilation 对象,它和 compiler 是有区别的:
compiler:webpack 刚构建时就会创建 compiler 对象,存在于 webpack 整个生命周期 。
compilation:在准备编译某一个模块的时候才会创建,主要存在于 compile 到 make 这一段生命周期里面。
开启 wacth 对文件进行监听时,文件发生改变需要重新编译时,只需要重新创建一个 compilation 对象即可,不需要重新创建 compiler 对象做很多第一步初始化操作。如果改变了 config,则需要重新执行 dev/build 命令,创建新的 compiler 对象。
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 文本。parse
方法将 JS 代码解析成为 AST 结构。hooks.exportImportSpecifier
。module.addDependency
将依赖对象加入到 module 依赖列表中。module.handleParseResult
处理模块依赖。handleModuleCreate
,控制流回到第一步。在整个过程中数据流 module ⇒ AST ⇒ dependency ⇒ module 的转变,将源码转为 AST 主要是为了分析模块的 import 语句收集相关依赖数组,最后遍历 dependences 数组将 Dependency 转换为 Module 对象,之后递归处理这些新的 Module,直到所有项目文件处理完毕。
总结来说就是,从入口文件开始收集其依赖模块,并对依赖模块再进行相同的模块处理。
构建过程
例如上图,entry 文件为 index.js,分别依赖 a.js/b.js,其中 a.js 又依赖 c.js/d.js 。
第一步
根据 webpack 初始化之后,能够确定入口文件 index.js,并调用compilation.addEntry
函数将之添加为 Module 对象。
第二步
通过 acorn 解析 index 文件,分析 AST 得到 index 有两个依赖。
第三步
得到了两个 dependence 之后,调用 module[index] 的 handleParserResult 方法处理 a/b 两个依赖对象。
第四步
又触发 module[a/b] 的 handleModuleCreation 方法,从 a 模块中又解析到 c/d 两个新依赖,于是再继续调用 module[a] 的 handleParseResult,递归上述流程。
第五步
最终得到 a/b/c/d 四个 Module 以及其对应的 dependence。
所有的模块构建完毕,没有新的依赖可以继续,由此进入生成阶段。
在构建阶段 make 结束之后,就会进入生成阶段,调用compilation.seal
表明正式进入生成阶段。
在 seal 阶段主要是是将构建阶段生成的 module 拆分组合到 chunk 对象中,再转译成为目标环境的产物,并写出为产物文件,解决的是资源输出问题。
ChunkGraph
对象hooks.optimizeDependencies
优化模块依赖关系compilation.entries
入口文件创建 chunks,调用 addChunk 为每一个入口添加 chunk 对象,并且遍历当前入口的 dependency 对象找到对应 module 对象关联到该 chunkoptimizeModules/optimizeChunks
等钩子,对 chunk 和 module 进行一系列的优化操作,这些优化操作都是有插件去完成的,例如 SplitChunksPlugincodeGeneration
方法生成 chunk 代码,会根据不同的 module 类型生成 template 代码createChunkAssets
方法为每一个 chunk 生成资产文件在 webpack 执行的三个阶段,对应着资源形态扭转,每一个阶段操作的对象都是不一样的
欢迎关注【袋鼠云数栈UED团队】~
袋鼠云数栈 UED 团队持续为广大开发者分享技术成果,相继参与开源了欢迎 star