Webpack 运行机制

概述 Webpack 的运行机制,不会讲解如何配置或者使用 Webpack。

基本概念

在了解 Webpack 原理前,需要掌握以下几个核心概念,以方便后面的理解:

  • Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。
  • Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
  • Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
  • Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
  • Plugin:扩展插件,在 Webpack 构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。

流程概述

简单来说,Webpack 的构建流程可以分为以下三大阶段:

  • 初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler。
  • 编译:从 Entry 发出,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理。
  • 输出:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统。

这是一个串行的过程,从启动到结束分为好几个阶段,每个阶段会发生很多的事件,Webpack 会把这些事件广播出来,所谓的 Plugin 就是在监听到特定的事件后执行相关代码,这期间插件还可以调用 Webpack 提供的 API 来改变 Webpack 的运行结果。

详细流程

详细的步骤我们参照下图来说:

注:图中并未展示所有事件,可参考 Webpack 文档。图片来源见文末。

初始化

  • 初始化参数

这个阶段 webpack 会将用户配置的 webpack.config.jsshell 脚本传过来的参数整合成 options 对象传到了下一个流程的控制对象中。合并参数过程中 webpack 也会根据参数中的信息来执行插件实例化语句new Plugin()

1
2
3
4
5
6
7
8
9
// options 作为最后返回结果,包含了之后构建阶段所需的重要信息。
{
entry: {},//入口配置
output: {}, //输出配置
plugins: [], //插件集合(配置文件 + shell指令)
module: { loaders: [ [Object] ] }, //模块配置
context: //工程路径
...
}
  • 实例化 Compiler

用上一步得到的参数初始化 Compiler 实例,Compiler 负责文件监听和启动编译。Compiler 实例中包含了完整的 Webpack 配置,且全局只有一个 Compiler 实例。这个时候,真正的 webpack 对象才刚被初始化,具体的初始化逻辑在 lib/webpack.js 中:

1
2
3
4
5
6
function webpack(options) {
var compiler = new Compiler();
...// 检查 options,若 watch 字段为 true,则开启 watch 线程
return compiler;
}
...
  • 加载插件

有了 Compiler 实例之后,开始加载插件,依次调用插件的 apply 方法,让插件可以监听后续的所有事件节点。同时给插件传入 compiler 实例的引用,以方便插件通过 compiler 调用 Webpack 提供的 API。

  • entry-option

遍历配置文件中所有入口,为每个 Entry 实例化一个对应的 EntryPlugin,为后面该 Entry 的递归解析工作做准备。

  • after-plugins

调用完所有内置的和配置的插件的 apply 方法。

  • after-resolvers

根据配置初始化完 resolver,resolver 负责在文件系统中寻找指定路径的文件。

编译

webpack 的实际入口是 Compiler 中的 run 方法,run 一旦执行后,就开始了编译和构建流程 ,其中有几个比较关键的 webpack 事件节点。

  • compile:开始编译

compiler.run 后首先会触发 compile 事件 ,这一步会构建出 Compilation 对象。

  • compilation 对象

当 Webpack 以开发模式运行时,每当检测到文件变化,一次新的 Compilation 将被创建。一个 Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。这个对象有两个作用,一是负责组织整个打包过程,包含了每个构建环节及输出环节所对应的方法,在每一个节点都会触发 webpack 事件去调用各插件。二是该对象内部存放着所有 module ,chunk,生成的 asset 以及用来生成最后打包文件的 template 的信息。

在编译阶段中,最重要的要数 compilation 事件了,因为在 compilation 阶段调用了 Loader 完成了每个模块的转换操作,在 compilation 阶段又包括很多小的事件,它们分别是:

1
2
3
4
build-module:使用对应的 Loader 去转换一个模块。
normal-module-loader:在用 Loader 对一个模块转换完后,使用 acorn 解析转换后的内容,输出对应的抽象语法树(AST),以方便 Webpack -后面对代码的分析。
program:从配置的入口模块开始,分析其 AST,当遇到 require 等导入其它模块语句时,便将其加入到依赖的模块列表,同时对新找出的依赖模块递归分析,最终搞清所有模块的依赖关系。
seal:所有模块及其依赖的模块都通过 Loader 转换完成后,根据依赖关系开始生成 Chunk。

输出

  • should-emit:所有需要输出的文件已经生成好,询问插件哪些文件需要输出,哪些不需要。
  • emit:确定好要输出哪些文件后,执行文件输出,可以在这里获取和修改输出内容。
  • after-emit:文件输出完毕。
  • done:成功完成一次完成的编译和输出流程。
  • failed:如果在编译和输出流程中遇到异常导致 Webpack 退出时,就会直接跳转到本步骤,插件可以在本事件中获取到具体的错误原因。
    在输出阶段已经得到了各个模块经过转换后的结果和其依赖关系,并且把相关模块组合在一起形成一个个 Chunk。 在输出阶段会根据 Chunk 的类型,使用对应的模版生成最终要要输出的文件内容。

最后

要详细的把 Webpack 的整个流程说下来,一篇文章肯定是不够的。这篇文章只是大概的梳理了一下,讲的很粗糙,后续有了新的理解还会更新。作者水平有限,还请轻喷~

参考

  • 淘宝前端团队
  • Webpack运行机制
  • 深入浅出Webpack