Webpack编译原理总览
说明
总览Webpack,Webpack编译原理、流程,Webpack plugin的基本架构,compiler与compilation
# 基本流程
初始化阶段:
- 初始化参数:从配置文件、 配置对象、Shell 参数中读取,与默认配置结合得出最终的参数
- 创建编译器对象:用上一步得到的参数创建
Compiler
对象 - 初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化 RuleSet 集合、加载配置的插件等
- 开始编译:执行
compiler
对象的run
方法 - 确定入口:根据配置中的
entry
找出所有的入口文件,调用compilition.addEntry
将入口文件转换为dependence
对象
构建阶段:
- 编译模块(make):根据
entry
对应的dependence
创建module
对象,调用loader
将模块转译为标准 JS 内容,调用 JS 解释器将内容转换为 AST 对象,从中找出该模块依赖的模块,再 递归 本步骤直到所有入口依赖的文件都经过了本步骤的处理 - 完成模块编译:上一步递归处理所有能触达到的模块后,得到了每个模块被翻译后的内容以及它们之间的 依赖关系图
生成阶段:
- 输出资源(seal):根据入口和模块之间的依赖关系,组装成一个个包含多个模块的
Chunk
,再把每个Chunk
转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会 - 写入文件系统(emitAssets):在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
# webpack基本工作原理
依赖分析
webpack主要功能是从入口文件开始读取文件源码,找到它的依赖。然后读取依赖文件,继续找它们的依赖,一直递归下去。
模块映射
在依赖分析期间,webpack会进行模块映射,把分析过的文件内容都放在一个map中(大文件),通过文件路径及文件名相关联的值作为key。
# 生命周期
在 Compiler 运行的过程中,webpack 定义了许多生命周期。先放一张总览图(排除 watch 和错误处理):正是由于有如此多的生命周期开放出来,所以 webpack 的插件机制很强大,可以灵活的介入编译的各个环节
# 插件API
- compiler: 顶级API,
Compiler
类的实例,webpack 从开始执行到结束,Compiler
只会实例化一次。compiler 对象记录了 webpack 运行环境的所有的信息,插件可以通过它获取到 webpack 的配置信息,如entry、output、module
等配置。 - compilation: 通过compiler访问,
Compilation
类实例,提供了 webpack 大部分生命周期Hook API供自定义处理时做拓展使用。一个 compilation 对象记录了一次构建到生成资源过程中的信息,它储存了当前的模块资源、编译生成的资源、变化的文件、以及被跟踪依赖的状态信息。 - Resolvers: webpack会把entry的配置路径提供给 Resolver,它会检查给定的部分路径是否存在,并返回完整的绝对路径以及上下文、请求、调用等额外信息。在
compiler
类中,提供了三种类型的内置解析器:normal
: 通过绝对或相对路径解析模块;context
: 在给定的上下文中解析模块。;loader
: 解析 webpack loader。 - NormalModuleFactory 与 ContextModuleFactory Hooks: 工厂创建对象或实例。从入口点开始,它解析每个请求,解析内容以查找进一步的请求,并通过解析所有文件和解析任何新文件来继续爬取文件。在最后阶段,每个依赖项都成为一个 Module 实例。
- JavascriptParser: 提供模块解释的API,让开发者可以自定义处理模块解释的过程。
# Tapable
Tapable (opens new window) 是 Webpack 插件架构的核心支架,本质上就是围绕着 订阅/发布 模式叠加各种特化逻辑
Webpack API都继承了Tapable类。Tapable使用发布订阅模式,封装了一系列API控制钩子函数的发布与订阅。
tapable模块下4个类:
- SyncHook: 同步钩子,任务会从先到后依次逐个执行。
- SyncBailHook: 确保同步钩子,提供终止机制中断钩子任务执行。按顺序执行注册的消费者回调,一旦其中一个消费者return返回值,立刻中断后续执行
- AsyncSeriesHook: 异步串行任务钩子。
- AsyncParallelHook: 异步并行任务钩子。
# compiler 和 compilation
Compiler
模块是 webpack 的主要引擎,它通过 CLI (opens new window) 或者 Node API (opens new window) 传递的所有选项创建出一个 compilation 实例。 它扩展(extends)自 Tapable
类,用来注册和调用插件。 大多数面向用户的插件会首先在 Compiler
上注册。
Compilation
的职责就是对所有 require 图(graph)中对象的字面上的编译,编译构建 module 和 chunk,并调用插件构建过程,同时把本次构建编译的内容全存到内存里。webpack 运行过程中只会有一个 compiler
;而每次编译 包括调用 compiler.run
函数或者 watch = true
时文件发生变更,都会创建一个 compilation
对象,compilation 编译可以多次执行,如在watch模式下启动 webpack,每次监测到源文件发生变化,都会重新实例化一个compilation对象,从而生成一组新的编译资源对象。这个对象可以访问所有的模块和它们的依赖。
compiler
对象记录着构建过程中 webpack
环境与配置信息,整个 webpack
从开始到结束的生命周期。针对的是webpack。
compilation
对象记录编译模块的信息,只要项目文件有改动,compilation
就会被重新创建。针对的是随时可变的项目文件。
# webpack plugin 的基本架构
插件需要封装成class,当webpack安装插件时,会对class进行实例化,并调用实例下的apply
方法。调用apply
方法时候,会把compiler
对象作为参数的形式传入,compiler
是webpack底层编译对象的引用,开发者可以在apply
方法实现中使用compiler
。
class HelloCompilationPlugin {
apply(compiler) {
// 指定一个挂载到 compilation 的钩子,回调函数的参数为 compilation 。
compiler.hooks.compilation.tap('HelloCompilationPlugin', (compilation) => {
// 现在可以通过 compilation 对象绑定各种钩子
console.log('现在可以通过 compilation 对象绑定各种钩子')
compilation.hooks.optimize.tap('HelloCompilationPlugin', () => {
console.log('资源已经优化完毕。');
});
});
}
}
module.exports = HelloCompilationPlugin;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 参考
[万字总结] 一文吃透 Webpack 核心原理 - 掘金 (juejin.cn) (opens new window)
webpack 编译原理(How webpack compiles) - 掘金 (juejin.cn) (opens new window)
webpack详解 - 掘金 (juejin.cn) (opens new window)
webpack plugin 从入门到入门 - 掘金 (juejin.cn) (opens new window)
webpack plugin 从入门到入门 之compiler与compilation - 掘金 (juejin.cn) (opens new window)
webpack 中文文档 | webpack 中文文档 | webpack 中文网 (webpackjs.com) (opens new window)
掘金小册 《Webpack5 核心原理与应用实践》