1、工作原理剖析

  1. Webpack CLI 启动打包流程;
  2. 载入 Webpack 核心模块,创建 Compiler 对象;
  3. 使用 Compiler 对象开始编译整个项目;
  4. 从入口文件开始,解析模块依赖,形成依赖关系树;
  5. 递归依赖树,将每个模块交给对应的 Loader 处理;
  6. 合并 Loader 处理完的结果,将打包结果输出到 dist 目录。

整个打包过程中,Loader 机制起了很重要的作用,因为如果没有 Loader 的话,Webpack 就无法实现各种各样类型的资源文件加载,那 Webpack 也就只能算是一个用来合并 JS 模块代码的工具了。

插件机制,它并不会影响 Webpack 的核心工作过程,只是 Webpack 为了提供一个强大的扩展能力,它为整个工作过程的每个环节都预制了一个钩子,也就是说我们可以通过插件往 Webpack 工作过程的任意环节植入一些自定义的任务,从而扩展 Webpack 打包功能以外的能力。

2、步骤

2.1 Webpack CLI

首先,Webpack CLI 会通过 yargs 模块解析 CLI 参数,例如 —mode=production;
image.png

紧接着后面,调用了 bin/utils/convert-argv.js 模块,将得到的命令行参数转换为 Webpack 的配置选项对象;
image.png

在 convert-argv.js 工作过程中,首先为传递过来的命令行参数设置了默认值,然后判断了命令行参数中是否指定了一个具体的配置文件路径,如果指定了就加载指定配置文件,反之则需要根据默认配置文件加载规则找到配置文件,具体代码如下:
image.png

有了配置选项过后,开始载入 Webpack 核心模块,传入配置选项,创建 Compiler 对象;
image.png

2.2 创建 Compiler 对象

模块的入口文件,也就是 lib/webpack.js 文件,这个文件导出的是一个用于创建 Compiler 的函数,具体如下:
image.png

options 不仅仅可以是一个对象,还可以是一个数组,如果我们传入的是一个数组,那么 Webpack 内部创建的就是一个 MultiCompiler,也就是说 Webpack 应该支持同时开启多路打包,配置数组中的每一个成员就是一个独立的配置选项。而如果我们传入的是普通的对象,就会按照我们最熟悉的方式创建一个 Compiler 对象,进行单线打包。
image.png

在创建了 Compiler 对象过后,Webpack 就开始注册我们配置中的每一个插件了,因为再往后 Webpack 工作过程的生命周期就要开始了,所以必须先注册,这样才能确保插件中的每一个钩子都能被命中。
image.png

2.3 开始构建

完成 Compiler 对象的创建过后,紧接着这里的代码开始判断配置选项中是否启用了监视模式,具体操作如下:
image.png

这个 run 方法定义在 Compiler 类型中,具体文件在 webpack 模块下的 lib/Compiler.js 中,代码位置如下:
image.png

这个方法内部就是先触发了beforeRun 和 run 两个钩子,然后最关键的是调用了当前对象的 compile 方法,真正开始编译整个项目,具体代码位置如下:
image.png

compile 方法内部主要就是创建了一个 Compilation 对象,Compilation 字面意思是“合集”,实际上,你就可以理解为一次构建过程中的上下文对象,里面包含了这次构建中全部的资源和信息。
image.png
创建完 Compilation 对象过后,紧接着触发了一个叫作 make 的钩子,进入整个构建过程最核心的 make 阶段。

2.4 make 阶段

make 阶段主体的目标就是:根据 entry 配置找到入口模块,开始依次递归出所有依赖,形成依赖关系树,然后将递归到的每个模块交给不同的 Loader 处理。
image.png

webpack使用内置插件系统;
image.png
这个插件中调用了 Compilation 对象的 addEntry 方法,开始解析我们源代码中的入口文件,以此开始“顺藤摸瓜”式的寻找。

对于 make 阶段后续的流程,这里我们概括一下:

  1. SingleEntryPlugin 中调用了 Compilation 对象的 addEntry 方法,开始解析入口;
  2. addEntry 方法中又调用了 _addModuleChain 方法,将入口模块添加到模块依赖列表中;
  3. 紧接着通过 Compilation 对象的 buildModule 方法进行模块构建;
  4. buildModule 方法中执行具体的 Loader,处理特殊资源加载;
  5. build 完成过后,通过 acorn 库生成模块代码的 AST 语法树;
  6. 根据语法树分析这个模块是否还有依赖的模块,如果有则继续循环 build 每个依赖;
  7. 所有依赖解析完成,build 阶段结束;
  8. 最后合并生成需要输出的 bundle.js 写入 dist 目录。