Open lance10030 opened 5 years ago
webpack
在阅读 webpack4.x 源码的过程中,参考了《深入浅出webpack》一书和众多大神的文章,结合自己的一点体会,总结如下。
webpack4.x
webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。 webpack 通过 Tapable 来组织这条复杂的生产线。 webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。 --吴浩麟《深入浅出webpack》
Tapable
entry,loader,plugin,module,chunk 不论文档还是相关的介绍都很多了,不赘述,有疑问的移步文档。
entry
loader
plugin
module
chunk
webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
Shell
Compiler
run
Chunk
Compile
compilation
Compilation
const { Tapable, SyncHook, SyncBailHook, AsyncParallelHook, AsyncSeriesHook } = require("tapable");
class Compiler extends Tapable { constructor(context) { super(); this.hooks = { / @type {SyncBailHook} */ //所有需要输出的文件已经生成好,询问插件哪些文件需要输出,哪些不需要。 shouldEmit: new SyncBailHook(["compilation"]), /* @type {AsyncSeriesHook} / //成功完成一次完成的编译和输出流程。 done: new AsyncSeriesHook(["stats"]), / @type {AsyncSeriesHook<>} */ additionalPass: new AsyncSeriesHook([]), / @type {AsyncSeriesHook} */ beforeRun: new AsyncSeriesHook(["compiler"]), /* @type {AsyncSeriesHook} / //启动一次新的编译 run: new AsyncSeriesHook(["compiler"]), / @type {AsyncSeriesHook} */ // 确定好要输出哪些文件后,执行文件输出,可以在这里获取和修改输出内容。 emit: new AsyncSeriesHook(["compilation"]), / @type {AsyncSeriesHook} */ // 输出完毕 afterEmit: new AsyncSeriesHook(["compilation"]), // 以上几个事件(除了run,beforerun为编译阶段)其余为输出阶段的事件 /* @type {SyncHook<Compilation, CompilationParams>} / // compilation 创建之前挂载插件的过程 thisCompilation: new SyncHook(["compilation", "params"]), / @type {SyncHook<Compilation, CompilationParams>} */ // 创建compilation对象 compilation: new SyncHook(["compilation", "params"]), /* @type {SyncHook} / // 初始化阶段:初始化compilation参数 normalModuleFactory: new SyncHook(["normalModuleFactory"]), /* @type {SyncHook} / // 初始化阶段:初始化compilation参数 contextModuleFactory: new SyncHook(["contextModulefactory"]),
/** @type {AsyncSeriesHook<CompilationParams>} */ beforeCompile: new AsyncSeriesHook(["params"]), /** @type {SyncHook<CompilationParams>} */ // 该事件是为了告诉插件一次新的编译将要启动,同时会给插件带上 compiler 对象 compile: new SyncHook(["params"]), /** @type {AsyncParallelHook<Compilation>} */ //一个新的 Compilation 创建完毕,即将从 Entry 开始读取文件,根据文件类型和配置的 Loader 对文件进行编译,编译完后再找出该文件依赖的文件,递归的编译和解析。 make: new AsyncParallelHook(["compilation"]), /** @type {AsyncSeriesHook<Compilation>} */ // 一次Compilation执行完成 afterCompile: new AsyncSeriesHook(["compilation"]), /** @type {AsyncSeriesHook<Compiler>} */ //监听模式下启动编译(常用于开发阶段) watchRun: new AsyncSeriesHook(["compiler"]), /** @type {SyncHook<Error>} */ failed: new SyncHook(["error"]), /** @type {SyncHook<string, string>} */ invalid: new SyncHook(["filename", "changeTime"]), /** @type {SyncHook} */ // 如名字所述 watchClose: new SyncHook([]), // TODO the following hooks are weirdly located here // TODO move them for webpack 5 /** @type {SyncHook} */ //初始化阶段:开始应用 Node.js 风格的文件系统到compiler 对象,以方便后续的文件寻找和读取。 environment: new SyncHook([]), /** @type {SyncHook} */ // 参照上文 afterEnvironment: new SyncHook([]), /** @type {SyncHook<Compiler>} */ // 调用完内置插件以及配置引入插件的apply方法,完成了事件订阅 afterPlugins: new SyncHook(["compiler"]), /** @type {SyncHook<Compiler>} */ afterResolvers: new SyncHook(["compiler"]), /** @type {SyncBailHook<string, EntryOptions>} */ // 读取配置的 Entrys,为每个 Entry 实例化一个对应的 EntryPlugin,为后面该 Entry 的递归解析工作做准备。 entryOption: new SyncBailHook(["context", "entry"]) };
在 `webpack` 执行的过程中,会按顺序广播一系列事件--`this.hooks`中的一系列事件(类似于我们常用框架中的生命周期),而这些事件的订阅者该按照怎样的顺序来组织,来执行,来进行参数传递... 这就是 `Tapable` 要做的事情。 关于 `Tapable` 给大家推荐一篇比较好(但是阅读量点赞评论都不多2333)的[科普文](https://juejin.im/post/5abf33f16fb9a028e46ec352) ## 流程细节 流程细节参照我在引用的`Compile`对象中的注释,有一点需要注意,作者`hooks`的书写顺序并不是调用顺序。 有些没注释的有几种情况: 1. 不那么重要,或参照事件名称和上下文可知 2. 主要是暂时还不知道(2333,后面有新的理解再补充,逃...) 3. 当然最重要的事件基本涵盖到了 这里补充一个大从参考文章里面找来的图 ### compilation 过程简介 `compilation` 实际上就是调用相应的 `loader` 处理文件生成 `chunks`并对这些 `chunks` 做优化的过程。几个关键的事件(Compilation对象this.hooks中): 1. `buildModule` 使用对应的 `Loader` 去转换一个模块; 2. `normalModuleLoader` 在用 `Loader` 对一个模块转换完后,使用 `acorn` 解析转换后的内容,输出对应的抽象语法树(AST),以方便 `webpack` 后面对代码的分析。 3. `seal` 所有模块及其依赖的模块都通过 `Loader` 转换完成后,根据依赖关系开始生成 `Chunk`。 最后从参考文章中摘了一张图片以便于对整个过程有更清晰的认知 ![image](https://raw.githubusercontent.com/BooheeFE/weekly/master/assets/img/webpack-process.jpg) ## 参考 1. http://taobaofed.org/blog/2016/09/09/webpack-flow/ 2. http://imweb.io/topic/5baca58079ddc80f36592f1a 3. 《深入浅出webpack》 ## 广而告之 本文发布于[薄荷前端周刊](https://github.com/BooheeFE/weekly),欢迎Watch & Star ★,转载请注明出处。 ### 欢迎讨论,点个赞再走吧 。◕‿◕。 ~
webpack
原理与实践(一):打包流程写在前面的话
在阅读
webpack4.x
源码的过程中,参考了《深入浅出webpack》一书和众多大神的文章,结合自己的一点体会,总结如下。总述
webpack
就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。webpack
通过Tapable
来组织这条复杂的生产线。webpack
在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。webpack
的事件流机制保证了插件的有序性,使得整个系统扩展性很好。 --吴浩麟《深入浅出webpack》核心的概念
entry
,loader
,plugin
,module
,chunk
不论文档还是相关的介绍都很多了,不赘述,有疑问的移步文档。构建流程
webpack
的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:Shell
语句中读取与合并参数,得出最终的参数;Compiler
对象,加载所有配置的插件,执行对象的run
方法开始执行编译;Chunk
,再把每个Chunk
转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;webpack
会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用webpack
提供的 API 改变webpack
的运行结果。webpack 中比较核心的两个对象
Compile
对象:负责文件监听和启动编译。Compiler
实例中包含了完整的webpack
配置,全局只有一个Compiler
实例。compilation
对象:当webpack
以开发模式运行时,每当检测到文件变化,一次新的Compilation
将被创建。一个Compilation
对象包含了当前的模块资源、编译生成资源、变化的文件等。Compilation
对象也提供了很多事件回调供插件做扩展。Compile
为例class Compiler extends Tapable { constructor(context) { super(); this.hooks = { / @type {SyncBailHook} */
//所有需要输出的文件已经生成好,询问插件哪些文件需要输出,哪些不需要。
shouldEmit: new SyncBailHook(["compilation"]),
/* @type {AsyncSeriesHook} /
//成功完成一次完成的编译和输出流程。
done: new AsyncSeriesHook(["stats"]),
/ @type {AsyncSeriesHook<>} */
additionalPass: new AsyncSeriesHook([]),
/ @type {AsyncSeriesHook} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/* @type {AsyncSeriesHook} /
//启动一次新的编译
run: new AsyncSeriesHook(["compiler"]),
/ @type {AsyncSeriesHook} */
// 确定好要输出哪些文件后,执行文件输出,可以在这里获取和修改输出内容。
emit: new AsyncSeriesHook(["compilation"]),
/ @type {AsyncSeriesHook} */
// 输出完毕
afterEmit: new AsyncSeriesHook(["compilation"]),
// 以上几个事件(除了run,beforerun为编译阶段)其余为输出阶段的事件
/* @type {SyncHook<Compilation, CompilationParams>} /
// compilation 创建之前挂载插件的过程
thisCompilation: new SyncHook(["compilation", "params"]),
/ @type {SyncHook<Compilation, CompilationParams>} */
// 创建compilation对象
compilation: new SyncHook(["compilation", "params"]),
/* @type {SyncHook} /
// 初始化阶段:初始化compilation参数
normalModuleFactory: new SyncHook(["normalModuleFactory"]),
/* @type {SyncHook} /
// 初始化阶段:初始化compilation参数
contextModuleFactory: new SyncHook(["contextModulefactory"]),