关于 Webpack Plugin 的原理与开发
关于 Webpack Plugin 的原理与开发
创建插件
webpack 插件由以下组成:
- 一个 JavaScript 命名函数或 JavaScript 类。
- 在插件函数的 prototype 上定义一个
apply方法。 - 指定一个绑定到 webpack 自身的事件钩子 (opens in a new tab)。
- 处理 webpack 内部实例的特定数据。
- 功能完成后调用 webpack 提供的回调。
// 一个 JavaScript 类
class MyExampleWebpackPlugin {
// 在插件函数的 prototype 上定义一个 `apply` 方法,以 compiler 为参数。
apply(compiler) {
// 指定一个挂载到 webpack 自身的事件钩子。
compiler.hooks.emit.tapAsync(
'MyExampleWebpackPlugin',
(compilation, callback) => {
console.log('这是一个示例插件!');
console.log(
'这里表示了资源的单次构建的 `compilation` 对象:',
compilation
);
// 用 webpack 提供的插件 API 处理构建过程
compilation.addModule(/* ... */);
callback();
}
);
}
}基本插件架构
插件是由「具有 apply 方法的 prototype 对象」所实例化出来的。
这个 apply 方法在安装插件时,会被 webpack compiler 调用一次。apply 方法可以接收一个 webpack compiler 对象的引用,从而可以在回调函数中访问到 compiler 对象。
一个插件结构如下:
class HelloWorldPlugin {
apply(compiler) {
compiler.hooks.done.tap(
'Hello World Plugin',
(
stats /* 绑定 done 钩子后,stats 会作为参数传入。 */
) => {
console.log('Hello World!');
}
);
}
}
module.exports = HelloWorldPlugin;然后,要安装这个插件,只需要在你的 webpack 配置的 plugin 数组中添加一个实例:
// webpack.config.js
var HelloWorldPlugin = require('hello-world');
module.exports = {
// ... 这里是其他配置 ...
plugins: [new HelloWorldPlugin({ options: true })],
};使用 schema-utils (opens in a new tab) 来校验传入插件的选项。
这里是个例子
import { validate } from 'schema-utils';
// 选项对象的 schema
const schema = {
type: 'object',
properties: {
test: {
type: 'string',
},
},
};
export default class HelloWorldPlugin {
constructor(options = {}) {
validate(schema, options, {
name: 'Hello World Plugin',
baseDataPath: 'options',
});
}
apply(compiler) {}
}Compiler 和 Compilation
在插件开发中最重要的两个资源就是 compiler (opens in a new tab) 和 compilation (opens in a new tab) 对象。
理解它们的角色是扩展 webpack 引擎重要的第一步。
class HelloCompilationPlugin {
apply(compiler) {
// 指定一个挂载到 compilation 的钩子,
// 回调函数的参数为 compilation 。
compiler.hooks.compilation.tap('HelloCompilationPlugin', (compilation) => {
// 现在可以通过 compilation 对象绑定各种钩子
compilation.hooks.optimize.tap('HelloCompilationPlugin', () => {
console.log('资源已经优化完毕。');
});
});
}
}
module.exports = HelloCompilationPlugin;compiler 和 compilation 以及其他重要对象提供的钩子清单,请查看 plugins API (opens in a new tab) 文档
这个还是要看webpack 官方文档 里面的hooks 和 API 太多了 按需去了解 不必全部看完 常用的 一些 hook 和 api 即可
异步编译插件
有些插件钩子是异步的。我们可以像同步方式一样用 tap 方法来绑定,也可以用 tapAsync 或 tapPromise 这两个异步方法来绑定
tapAsync
当我们用 tapAsync 方法来绑定插件时,_必须_调用函数的最后一个参数 callback 指定的回调函数
class HelloAsyncPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync(
'HelloAsyncPlugin',
(compilation, callback) => {
// 执行某些异步操作...
setTimeout(function () {
console.log('异步任务完成...');
callback();
}, 1000);
}
);
}
}
module.exports = HelloAsyncPlugin;tapPromise
当我们用 tapPromise 方法来绑定插件时,_必须_返回一个 pormise ,异步任务完成后 resolve
class HelloAsyncPlugin {
apply(compiler) {
compiler.hooks.emit.tapPromise('HelloAsyncPlugin', (compilation) => {
// 返回一个 pormise ,异步任务完成后 resolve
return new Promise((resolve, reject) => {
setTimeout(function () {
console.log('异步任务完成...');
resolve();
}, 1000);
});
});
}
}
module.exports = HelloAsyncPlugin;示例
一旦能我们深入理解 webpack compiler 和每个独立的 compilation,我们依赖 webpack 引擎将有无限多的事可以做。
我们可以重新格式化已有的文件,创建衍生的文件,或者制作全新的生成文件。
让我们来写一个简单的示例插件,生成一个叫做 assets.md 的新文件;
文件内容是所有构建生成的文件的列表。这个插件大概像下面这样:
class FileListPlugin {
static defaultOptions = {
outputFile: 'assets.md',
};
// 需要传入自定义插件构造函数的任意选项
//(这是自定义插件的公开API)
constructor(options = {}) {
// 在应用默认选项前,先应用用户指定选项
// 合并后的选项暴露给插件方法
// 记得在这里校验所有选项
this.options = { ...FileListPlugin.defaultOptions, ...options };
}
apply(compiler) {
const pluginName = FileListPlugin.name;
// webpack 模块实例,可以通过 compiler 对象访问,
// 这样确保使用的是模块的正确版本
// (不要直接 require/import webpack)
const { webpack } = compiler;
// Compilation 对象提供了对一些有用常量的访问。
const { Compilation } = webpack;
// RawSource 是其中一种 “源码”("sources") 类型,
// 用来在 compilation 中表示资源的源码
const { RawSource } = webpack.sources;
// 绑定到 “thisCompilation” 钩子,
// 以便进一步绑定到 compilation 过程更早期的阶段
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
// 绑定到资源处理流水线(assets processing pipeline)
compilation.hooks.processAssets.tap(
{
name: pluginName,
// 用某个靠后的资源处理阶段,
// 确保所有资源已被插件添加到 compilation
stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
},
(assets) => {
// "assets" 是一个包含 compilation 中所有资源(assets)的对象。
// 该对象的键是资源的路径,
// 值是文件的源码
// 遍历所有资源,
// 生成 Markdown 文件的内容
const content =
'# In this build:\n\n' +
Object.keys(assets)
.map((filename) => `- ${filename}`)
.join('\n');
// 向 compilation 添加新的资源,
// 这样 webpack 就会自动生成并输出到 output 目录
compilation.emitAsset(
this.options.outputFile,
new RawSource(content)
);
}
);
});
}
}
module.exports = { FileListPlugin };webpack.config.js
const { FileListPlugin } = require('./file-list-plugin.js');
// 在 webpack 配置中使用自定义的插件:
module.exports = {
// …
plugins: [
// 添加插件,使用默认选项
new FileListPlugin(),
// 或者:
// 使用任意支持的选项
new FileListPlugin({
outputFile: 'my-assets.md',
}),
],
};