frontendmasters webpack-plugins
2018-11-12 本文已影响0人
云峰yf
Tapable Plugin System
- webpack 是由插件组成的
- Tapable 是 webpack 的基石,编写和注册插件都离不开它,之前是靠 compiler 实例
- 继承了 Tapable 实例的类被称为 Tapable 实例
- 在类里有若干静态钩子,改善了之前的开发体验(把方法挂载到原型上),因此,所有不同的事件都是 webpack 生命周期的不同部分。
-
.tap
就像.plugin
的等价物,通过访问compiler.hooks.compilation
来做一些事情
Compiler & Compilation
- 有七个最重要的 Tapable 实例
- 第一个是 compiler,中央调度级别的,控制 webpack 的启动、构建完成、发射资产等,要访问其他实例必须通过它。
- 如果使用 node api,会得到
compiler.run
实例
- 如果使用 node api,会得到
- 第二个是 Compilation,最复杂的部分。是 webpack 构建依赖图的起点。
- 这是作用域提升的地方,也是我们摇树和所有启发式的地方。
Resolver & Module Factories
- 第三个是 resolver,你给它一个部分路径,它会确保它存在,然后给你完整的绝对路径。
- Resolver 实际上是你可以在 webpack 之外使用的东西。
- 第四个是 Module Factories,他们所做的只是创造实例。
- normal 模块工厂和 context 模块工厂,它们之间的区别在于支持动态 import 语句中的表达式
- 它将从文件中提取源代码。将它存储在内存中的这个模块对象中,
Parser & Templates
- 第五个是 Parser,将一串源代码转换为我们称之为AST或抽象语法树的代码。
- https://astexplorer.net/ 在线预览效果
- Webpack 默认使用 Acorn 作为解析器获得 token
- 解析器从模块工厂获取这个模块对象,将其转换为 AST,然后开始遍历图。
- 第六个是 Templates,做数据绑定。
- chunk、module、dep 都有模板,会调用一个名为 render 的函数。
- 最终得到一些 iife 函数
Compiler Walkthrough
- 我们将配置传递给 webpack,它读取 entry 属性。
- 我们不知道该条目是否存在,所以我们必须通过正常的模块工厂,该工厂调用解析器返回完整的路径信息,以及其他一些上下文和有用的数据,并将其传递回正常的模块工厂。
- 然后工厂创建了这个对象,收集源信息,现在我们有了模块的源代码。
- 接着解析器将代码解析为 AST。它遍历图形查找依赖语句,找到它时,它会附加到模块。
- 这是一个广度优先的搜索图遍历。重复这个过程,直到没有更多的依赖关系来解决。
- 最后进入渲染部分,依赖模板和依赖工厂把能执行在浏览器的代码生成。
- 可以把它分成三个主要阶段,或者像三个步骤。一:构建图。二:优化图,三,渲染它,我们只使用模板,所以它是数据绑定。
Plugin System Code Walkthrough
- tappable 实例会先调用 compiler.run,然后应用配置
- 里面有一个巨大的 switch 语句,用于创建不同的代码
- 接着在 Complier.hooks.entryoption.call 触发入口选项钩子,为依赖设置模板
- 最后得到一个模块,但钩子里可以插入不同的逻辑,得到不同的模块
- AST 的每个部分都会触发一个事件
- 实际上就是添加依赖项实例并将它们绑定在一起
- 创建一个解析器插件、创建一个依赖项工厂,就可以做任何事情了。每个依赖实例都有一个模板
- 修改 AST 很慢,因此我们选择做的是使用模板进行字符串转换。
Creating a Webpack Plugin
- 插件只是一个实例,或者它是一个具有 apply 方法的类。
- complier.hooks + 事件 + 触发方式写回调
- stats 是一个类,它有 Webpack compilation 实例。它告诉我们开始时间,整个包的哈希等有关构建的所有内容。
class MyFirstWebpackPlugin {
apply(compiler) {
compiler.hooks.done.tapAsync("MyFirstWebpackPlugin", (stats, cb) => {
const assetNames = [];
for (let assetName in stats.compilation.assets) {
assetNames.push(assetName);
}
console.log(assetNames.join("\n"));
cb();
});
compiler.hooks.compilation.tap("MyFirstWebpackPlugin", (compilation, params) => {
new MyFirstWebpackCompilationPlugin().apply(compilation);
});
}
}
class MyFirstWebpackCompilationPlugin {
apply(compilation) {
compilation.hooks.seal.tap("MyFirstWebpackPlugin", () => {
debugger;
});
}
}
module.exports = MyFirstWebpackPlugin;
Plugin Instance Hooks
- 如何插入不是 compilation 的东西呢?想插入不同的实例,仍然需要通过 compilation。
- render 之前先是 seal 阶段
- 在 NormalModuleFactory.beforeResolve 挂钩子改变引用的模块
- 比如 ContextReplacementPlugin,将 moment 从部分路径中解析
- 还有 NormalModuleReplacementPlugin
Isolating Plugins
- webpack 不调用文件系统,而是调用 MemoryFile 系统:memory-fs
- 如此抽象,就可以将源码存进 redis 或者 mongo 了
- 对于 resolver.resolve 的例外,不会做任何解析器调用。
- 比如可以使用 get 虚拟文件系统并编写一个解析器,用来完全替换 webpack 的解析器
- 一个插件可以应用另一个插件,从而隔离每个插件的功能集
Creating a Custom Loader
- 添加一个名为 resolveLoader 的属性
- 应用于解析模块的任意自定义行为,可以为你的加载器执行相同的操作。
- 有同步、异步,补丁 loader
- 如果你想在加载器之间共享公共 AST,那么传递一个抽象语法树作为第四个参数可以加快构建时间。
Configuring Babel for Webpack
- 使用 babel 会破坏 webpack 所有对 ES 模块的优化,所以用 babel 请设置 preset 的 module 为 false
Webpack Dev Kit & Wrap Up
留下了一个示例项目:Webpack Developer Kit
- https://github.com/TheLarkInn/webpack-developer-kit/blob/master/package.json
- 运行
npm run debug
来了解 webpack 内部原理