你想深入学习webpack吗?你想自己开发一个webpack plugin吗?我就问问,不想就算了吧!
了解一下什么是Plugin?
官方文档定义:插件是 webpack 的 支柱 功能。webpack 自身也是构建于你在 webpack 配置中用到的相同的插件系统之上!
插件目的在于解决 loader 无法实现的其他事。
Webpack Plugin其实就是一个类!!!
创建一个 Plugin
先学会一个简易版的plugin
// myPlugin.js
class MyPlugin {
constructor() {
console.log('Plugin 被创建了');
}
apply(compiler) {}
}
module.exports = MyPlugin;
// webpack.config.js
// 引入
const MyPlugin = require('myPlugin.js');
module.exports = {
plugins: [
new MyPlugin({title: 'MyPlugin'}),
]
};
我们先来看一下 Webpack ****官方给的案例:
const pluginName = 'ConsoleLogOnBuildWebpackPlugin';
class ConsoleLogOnBuildWebpackPlugin {
apply(compiler) {
// 代表开始读取 records 之前执行
compiler.hooks.run.tap(pluginName, compilation => {
console.log("webpack 构建过程开始!");
});
}
}
简单说明
- Plugin 其实就是一个类。
- 类需要一个 apply 方法,执行具体的插件方法。
- 插件方法做了一件事情就是在 run 这个 Hook 上注册了一个同步的打印日志的方法。
- apply 方法的入参注入了一个 compiler 实例,compiler 实例是 Webpack 的支柱引擎,代表了 CLI 和 Node API 传递的所有配置项。
- Hook 回调方法注入了 compilation 实例,compilation 能够访问当前构建时的模块和相应的依赖。
就这?就这?就这?
接下来讲解下基础知识点
什么是Hook?
Webpack 在编译的过程中会触发一系列流程,而在这样一连串的流程中,Webpack 把一些关键的流程节点暴露出来供开发者使用,这就是 Hook,可以类比 React / Vue的生命周期钩子。
Plugin 就是在这些 Hook 上暴露出方法供开发者做一些额外操作,因此在写 Plugin 的时候,也需要先了解我们应该在哪个 Hook 上做操作。
Webpack 共提供了以下十种 Hooks,代码中所有具体的 Hook 都是以下这 10 种中的一种。
// 源码取自:lib/index.js
"use strict";
exports.__esModule = true;
// 同步执行的钩子,不能处理异步任务
exports.SyncHook = require("./SyncHook");
// 同步执行的钩子,返回非空时,阻止向下执行
exports.SyncBailHook = require("./SyncBailHook");
// 同步执行的钩子,支持将返回值透传到下一个钩子中
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
// 同步执行的钩子,支持将返回值透传到下一个钩子中,返回非空时,重复执行
exports.SyncLoopHook = require("./SyncLoopHook");
// 异步并行的钩子
exports.AsyncParallelHook = require("./AsyncParallelHook");
// 异步并行的钩子,返回非空时,阻止向下执行,直接执行回调
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
// 异步串行的钩子
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
// 异步串行的钩子,返回非空时,阻止向下执行,直接执行回调
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
// 支持异步串行 && 并行的钩子,返回非空时,重复执行
exports.AsyncSeriesLoopHook = require("./AsyncSeriesLoopHook");
// 异步串行的钩子,下一步依赖上一步返回的值
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
// 以下 2 个是 hook 工具类,分别用于 hooks 映射以及 hooks 重定向
exports.HookMap = require("./HookMap");
exports.MultiHook = require("./MultiHook");
Hook 的类型可以通过官方 API 查询,地址传送门:www.webpackjs.com/api/compile…
核心工具库Tapable
Tapable是什么?webpack就是一个事件流,这个事件流是一个个插件组成的,而保证这些插件有序的运行就是Tapable的作用,它是采用发布订阅的架构模式。tapable中有很多钩子方法,用来搜集注册的plugin。简单来说Tapable就是webpack用来创建钩子的库。
Tapable 是 Webpack 核心工具库,它提供了所有 Hook 的抽象类定义,Webpack 许多对象都是继承自 Tapable 类。比如: tap,tapAsync和tapPrimise。
// 第二节 “创建一个 Plugin” 中说的 10 种 Hooks 都是继承了这两个类
// 源码取自:tapable.d.ts
declare class Hook<T, R, AdditionalOptions = UnsetAdditionalOptions> {
tap(options: string | Tap & IfSet<AdditionalOptions>, fn: (...args: AsArray<T>) => R): void;
}
declare class AsyncHook<T, R, AdditionalOptions = UnsetAdditionalOptions> extends Hook<T, R, AdditionalOptions> {
tapAsync(
options: string | Tap & IfSet<AdditionalOptions>,
fn: (...args: Append<AsArray<T>, InnerCallback<Error, R>>) => void
): void;
tapPromise(
options: string | Tap & IfSet<AdditionalOptions>,
fn: (...args: AsArray<T>) => Promise<R>
): void;
}
什么是compiler?
compiler包含了webpack环境所有的配置信息。 这个对象在启动webpack时被一次性建立,并配置好所有可操作的设置,包括options,loader和plugin。当在webpack环境中应用一个插件时,插件将收集到此compiler对象的引用。可以使用它来访问webpack的主环境。
什么是compilation?
compilation对象包含了当前模块资源、编译生成资源、变化的文件等。当运行webpack开发环境中间件时,没检测到一个文件变化,就会创建资源。
compilation对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。
compiler和compilation的区别?
Compiler 对象包含了 Webpack 环境所有的的配置信息,包含 options,loaders,plugins 这些信息,这个对象在 Webpack 启动时候被实例化,它是全局唯一的,可以简单地把它理解为 Webpack 实例;
Compilation 对象包含了当前的模块资源、编译生成资源、变化的文件等。当 Webpack 以开发模式运行时,每当检测到一个文件变化,一次新的 Compilation 将被创建。Compilation 对象也提供了很多事件回调供插件做扩展。通过 Compilation 也能读取到 Compiler 对象。
—— 摘自「深入浅出 Webpack」
- 生成时间不同,周期长度不同。compiler在webpack开始到关闭整个生命周期存在,而compilation只是代表了一次新的编译过程。
- compiler和compilation暴露出的钩子不同。
compiler常用的钩子
| Hook | type | 调用 |
|---|---|---|
| run | AsyncSeriesHook | 开始读取 records 之前 |
| compile | SyncHook | 一个新的编译(compilation)创建之后 |
| emit | AsyncSeriesHook | 生成资源到 output 目录之前 |
| done | SyncHook | 编译(compilation)完成 |
| initialize | SyncHook | 当编译器对象被初始化时调用(html-webpack-plugin中有用到) |
compilation常用的钩子
| Hook | type | 调用 |
|---|---|---|
| buildModule | SyncHook | 在模块构建开始之前触发。 |
| finishModules | SyncHook | 所有模块都完成构建。 |
| optimize | SyncHook | 优化阶段开始时触发。 |
其他的就不做过多介绍了,附上官网的链接webpack.docschina.org/api/compile…
完成一个完整的Webpack Plugin
Webpack 插件的名称都是叫xxx-webpack-plugin,Plugin demo就这样开始了。
Copyright-webpack-plugin
这是一个简单的demo,通过plugin生成一个markdown文件,文件中输出'written by zhulm'
// plugin class
class CopyrightWebpackPlugin {
constructor () {}
apply(compiler) {
// console.log('compiler: ', compiler);
compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin',
(compilation,cb) => {
// console.log('compilation: ', compilation);
compilation.assets['copyright.md'] = {
source: function() {
return 'written by zhulm';
},
size: function() {
return 15;
},
}
cb()
}
)
}
}
module.exports = CopyrightWebpackPlugin;
代码讲解:
- 主要是运用单compiler的钩子的emit方法,使用这个方法创建一个copyright.md文件,写入文字。
- tapAsync方法来调用钩子,需要转入两个参数,第一个参数是本身插件的名称,第二个参数是一个函数。
- source是源码文件,而size是生成文件的大小,返回 15 表示生成的文件大小是 15 byte。
- assets: { [pathname: string]: Source } — 普通对象,其中 key 是 asset 的路径名,value 是 asset 的数据
详情见我的github地址:github.com/dakoujia/we…
OutputCurrentBuildInfoPlugin
这个插件是获取输出信息的一个插件,获取git的提交信息及时间
new OutputCurrentBuildInfoPlugin({
outputName: 'build-log.json',
dateFormatType: 'YYYY-MM-DD HH:mm:ss',
buildType: 'local'
}),
生成的build-log.json
// local
{"build_time":"2021-07-04 20:28:20","build_type":"local","git":{"currentBranch":"","build_user":{"name":"zhulm","email":"liangming.zhu@foxmail.com"}}}
// git
{"build_time":"2021-07-04 21:26:59","build_type":"git","git":{"last_commit":{"hash":"f94253ba4af5727f1b9bd48f9b0b225723d597a4","date":"2021-07-04T21:19:44+08:00","message":"docs(read me): README.md","refs":"HEAD -> main, origin/main, origin/HEAD","body":"提交人:朱良明\n","author_name":"zhulm","author_email":"liangming.zhu@foxmail.com"},"currentBranch":"main","build_user":{"name":"zhulm","email":"liangming.zhu@foxmail.com"}}}
详情见我的github地址:github.com/dakoujia/we…
遇到的问题
1.构建的时候报错
webpack版本问题导致的,webpack5.0以上的版本会有这个问题,将版本降到4.16.5可以解决;
参考文献
揭秘webpack plugin: juejin.cn/post/684490…
官网的plugin: webpack.docschina.org/plugins/