中无主而不止,外无正而不行。——庄子javascript
如今前端开发基本上都会用到react
、vue
,用到了前端mvc
、mvvm
框架,基本上都会涉及到打包发布,打包经常使用的工具就是webpack
、gulp
等等。常常使用天然也要了解一些他大体的流程也会方便使用。 首先要理解webpack中比较核心的概念:前端
coding split
的产物,咱们能够对一些代码打包成一个单独的chunk
,好比某些公共模块,去重,更好的利用缓存。或者按需加载某些功能模块,优化加载时间。在webpack3
及之前咱们都利用CommonsChunkPlugin
将一些公共代码分割成一个chunk
,实现单独加载。在webpack4
中CommonsChunkPlugin
被废弃,使用SplitChunksPlugin
webpack 执行流程和事件流以下图所示:vue
webpack编译过程当中一个比较重要的概念compiler、compilation,以下:java
Compiler
实例中包含了完整的 webpack
配置,全局只有一个 Compiler
实例。webpack
以开发模式运行时,每当检测到文件变化,一次新的 Compilation
将被建立。一个 Compilation
对象包含了当前的模块资源、编译生成资源、变化的文件等。Compilation
对象也提供了不少事件回调供插件作扩展。Webpack的运行流程是一个串行的过程,从启动到结束依次执行如下流程:react
若是只执行一次构建,以上阶段将会按照顺序各执行一次。但在开启监听模式下,流程将变为以下: webpack
下面具体介绍一下 webpack
的三个大阶段具体的小步。git
初始化阶段大体分为:github
shell
和配置文件文件的参数而且实例化Complier对象。事件名 | 解释 |
---|---|
初始化参数 | 从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。 这个过程当中还会执行配置文件中的插件实例化语句 new Plugin()。 |
实例化 Compiler | 用上一步获得的参数初始化 Compiler 实例,Compiler 负责文件监听和启动编译。Compiler 实例中包含了完整的 Webpack 配置,全局只有一个 Compiler 实例。 |
加载插件 | 依次调用插件的 apply 方法,让插件能够监听后续的全部事件节点。同时给插件传入 compiler 实例的引用,以方便插件经过 compiler 调用 Webpack 提供的 API 。 |
environment | 开始应用 Node.js 风格的文件系统到 compiler 对象,以方便后续的文件寻找和读取。 |
entry-option | 读取配置的 Entrys ,为每一个 Entry 实例化一个对应的 EntryPlugin ,为后面该 Entry 的递归解析工做作准备。 |
after-plugins | 调用完全部内置的和配置的插件的 apply 方法。 |
after-resolvers | 根据配置初始化完 resolver ,resolver 负责在文件系统中寻找指定路径的文件。 |
事件名 | 解释 |
---|---|
before-run | 清除缓存 |
run | 启动一次新的编译。 |
watch-run | 和 run 相似,区别在于它是在监听模式下启动的编译,在这个事件中能够获取到是哪些文件发生了变化致使从新启动一次新的编译。 |
compile | 该事件是为了告诉插件一次新的编译将要启动,同时会给插件带上 compiler 对象。 |
compilation | 当 Webpack 以开发模式运行时,每当检测到文件变化,一次新的 Compilation 将被建立。一个 Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。Compilation 对象也提供了不少事件回调供插件作扩展。 |
make | 一个新的 Compilation 建立完毕,即将从 Entry 开始读取文件,根据文件类型和配置的 Loader 对文件进行编译,编译完后再找出该文件依赖的文件,递归的编译和解析。 |
after-compile | 一次 Compilation 执行完成。这里会根据编译结果 合并出咱们最终生成的文件名和文件内容。 |
invalid | 当遇到文件不存在、文件编译错误等异常时会触发该事件,该事件不会致使 Webpack 退出。 |
这里主要最重要的就是compilation
过程,compilation
实际上就是调用相应的 loader
处理文件生成 chunks
并对这些 chunks
作优化的过程。几个关键的事件(Compilation
对象this.hooks
中):web
事件名 | 解释 |
---|---|
build-module | 使用对应的 Loader 去转换一个模块。 |
normal-module-loader | 在用 Loader 对一个模块转换完后,使用 acorn 解析转换后的内容,输出对应的抽象语法树(AST ),以方便 Webpack 后面对代码的分析。 |
program | 从配置的入口模块开始,分析其 AST ,当遇到 require 等导入其它模块语句时,便将其加入到依赖的模块列表,同时对新找出的依赖模块递归分析,最终搞清全部模块的依赖关系。 |
seal | 全部模块及其依赖的模块都经过 Loader 转换完成后,根据依赖关系开始生成 Chunk 。 |
事件名 | 解释 |
---|---|
should-emit | 全部须要输出的文件已经生成好,询问插件哪些文件须要输出,哪些不须要。 |
emit | 肯定好要输出哪些文件后,执行文件输出,能够在这里获取和修改输出内容。 |
after-emit | 文件输出完毕。 |
done | 成功完成一次完成的编译和输出流程。 |
failed | 若是在编译和输出流程中遇到异常致使 Webpack 退出时,就会直接跳转到本步骤,插件能够在本事件中获取到具体的错误缘由。 |
Webpack
能够将其理解是一种基于事件流的编程范例,一个插件合集。而将这些插件控制在webapck
事件流上的运行的就是webpack
本身写的基础类Tapable
。Webpack
的事件流机制应用了观察者模式,和 Node.js
中的 EventEmitter
很是类似。 Tapable 有四组成员函数:shell
Tapable
实例 的事件中。它的行为和 EventEmitter
的 on()
方法类似,用来注册一个处理函数/监听器,来在信号/事件发生时作一些事情。(AnyPlugin|function)[]):AnyPlugin
应该是一个拥有 apply
方法的类(也能够是一个对象,可是不常见),或者只是一个包含注册代码的函数。这个方法只调用插件的定义,从而将真正的事件监听器能够注册到 Tapable
实例的注册列表中。Tapable
实例能够经过使用这些函数,在指定的 hash
下应用全部的插件。这一组方法的行为和 EventEmitter
的 emit()
方法类似,使用多种策略细致地控制事件的触发。Tapable
的原型。上面核心的对象 Compiler
、Compilation
等都是继承于Tabable
类。能够直接在 Compiler
和 Compilation
对象上广播和监听器,方法以下:
/** * 广播出事件 * event-name 为事件名称,注意不要和现有的事件重名 * params 为附带的参数 */
compiler.apply('event-name',params);
/** * 监听名称为 event-name 的事件,当 event-name 事件发生时,函数就会被执行。 * 同时函数中的 params 参数为广播事件时附带的参数。 */
compiler.plugin('event-name',function(params) {
doSomeThing();
});
复制代码
同理,compilation.apply
和 compilation.plugin
使用方法和上面一致。
tapable库暴露了不少Hook(钩子)类,为插件提供挂载的钩子。
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
复制代码
以下图所示tapable上的钩子:
tapAsync
或 tapPromise
(以及 tap
),执行经过 callAsync
、promise
;tap
,执行经过 call
;具体的用法请看Tapable。
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行如下流程:
同时咱们也了解了webpack中比较核心的几个概念compiler
、compilation
、tapable
。