webpack 构建流程
webpack 可以分为 4 个阶段:
- 初始化阶段 - webpack
- 合并配置项
- 创建Compiler
- 注册插件
- 编译阶段 - build
- 读取入口文件
- 从入口文件开始编译
- 调用 loader 对源代码进行转换
- 借助 babel 解析为 AST 收集依赖模块
- 递归对依赖模块进行编译操作
- 生成阶段 - seal
- 创建 chunk 对象
- 生成 assets 对象
- 输出阶段 - emit
初始化阶段
读取和合并配置信息
首先会执行 webpack 函数读取和合并配置信息,配置信息来源主要有两种方式:
- 第一种是通过 webpack.config.js 做配置,该文件中主要包括:入口文件、输出位置、loader 和 plugin
- 第二种是通过命令行的形式做配置,比如 --mode=production。命令行的权要高于配置文件。
创建 complier 对象
然后通过 Compiler 构造函数,传入合并的配置项,创建 compiler 实例。
Compiler 构造函数中有 run 方法和 emitAssets 方法。当需要执行编译时就会调用 run 方法。
注册插件
接着注册插件。插件的目的是在合适的时机干预构建过程。
插件可以有两种形式:
- 函数
- 对象,需要提供一个 apply 方法,并接收 compiler 实例作为参数
注册插件的过程就是遍历配置文件中的 plugins 数组,并依次执行该插件。当插件为函数时:plugin.call(compiler, compiler)。如果插件是一个对象,需要提供 apply 方法:plugin.apply(compiler)。
初始化的阶段完成。
编译阶段
编译工作的起点是调用 compiler.run 方法。run 的主要工作:
- 发起构建通知,触发 hooks.run 通知相关插件;
- 创建 compilation 编译对象;
- 读取 entry 入口文件;
- 编译 entry 入口文件;
创建 compilation 对象
执行 const compilation = new Compilation(this);创建 compilation 对象。
compilation 对象具有 moduleCode、modules Set、entries Set、chunks Set、和 assets 对象。
模块的 build (代码构建)和 seal (代码生成)都是 compilation 对象实现的。
然后执行 compilation.buildI();开始编译模块。
读取 entry 入口文件
构建模块首先从 entry 入口模块开始,此时首要工作是根据配置文件拿到入口模块信息。
调用 getEntry 方法会拿到 entry 对象,数据结构为:{ [模块名]:[模块绝对路径] }。相对于启动目录的绝对路径。
最终获得了一个key为entryName,value为entryAbsolutePath的对象。
编译 entry 入口文件
拿到入口文件后,依次对每个入口进行构建。
构建阶段执行如下操作:
- 通过 fs 模块读取入口文件的内容;
- 调用 loader 来转换文件内容;
- 为模块创建 module 对象,通过 AST 解析源代码收集依赖,并改写依赖模块的路径;
- 如果存在依赖模块,递归进行上述三步操作;
通过入口文件调用 buildModule 创建的 module 保存到 this.entries 中。
loader 本身是一个 JS 函数,接收模块文件的源代码作为参数,经过加工改造后返回新的代码。
执行 webpack 模块编译逻辑:首先读取文件原始代码,然后调用 loader 进行处理,最后调用 webpack 进行模块编译 为模块创建 module 对象。
- 创建 module 对象;
- 将 module code 解析为 AST 语法树;
- 遍历 AST 去识别 require 模块语法,将模块收集在 module.dependencies 之中,并改写 require 语法为 webpack_require;
- 将修改后的 AST 转换为源代码;
- 若存在依赖模块,深度递归构建依赖模块。
module对象有:id、dependencies 和 name 属性。
id 表示当模块针对于 this.rootPath 的相对目录
dependencies 是一个Set,内部保存了该模块依赖的所以模块的模块id
name 表示该模块属于哪个入口文件
_source 表示该模块经过babel编译后的字符串代码
对于存在依赖的模块,会递归依赖深度遍历,对这些依赖调用 buildModule 方法创建 module 对象,然后将module 保存到 this.modules 中。
简言之:将每个入口文件编译后的对象加入 this.entries,将每个依赖的模块编译后的对象加入 this.modules。
生成阶段
在「生成阶段」,会根据 entry 创建对应 chunk 并从 this.modules 中查找被 entry 所依赖的 module 集合。
根据 entry 创建 chunk。根据入口文件和依赖模块组装chunks:
首先遍历 this.entries 集合,执行 createChunk 方法。在 createChunk 方法中创建 chunk 并将 chunk 添加到 this.chunks 集合中。
chunk 包含的属性:
- name 表示当前入口文件的名称
- entryModule 入口文件编译后的对象
- modules 该入口文件依赖的所有模块对象组成的数组
接着根据chunks生成assets内容。遍历 this.chunks ,执行 getSourceCode 方法获取结合 runtime webpack 模块机制的运行时代码,经过拼接生成最终的 assets 产物。
this.assets 结构:{ 'main.js': '生成的字符串代码...' };
运行时代码包含:webpack_modules、webpack_module_cache、webpack_require 和 webpack_exports。
webpack_modules:遍历 modules 数组拿到以 moduleId 为 key,module._source 为value 的数组,再join成为对象
webpack_require:用于从__webpack__module_cache__中根据 moduleId 拿到 exports 对象,如果__webpack_module_cache__中还没有 mdouleId,则新建一个包含exprots对象的对象。然后执行__webpack_modules__中moduleId对应的module._source代码,并将module,module.exports,webpack_require 作为参数传递进去。最后返回module.exports 对象。
输出阶段
调用 this.emitAssets 方法开始输出阶段。
首先会调用 emit 钩子:this.hooks.emit.call()。
然后创建输出目录。
再将 assets 中的内容写入文件系统中:遍历 this.assets 对象,调用fs.writeFileSync(filePath, assets[filePath])。
结束之后触发钩子:this.hooks.done.call()。
结束。

浙公网安备 33010602011771号