FrankKai.github.io
FrankKai.github.io copied to clipboard
[译]如何写一个webpack plugin?
原文: https://webpack.js.org/contribute/writing-a-plugin/ demo: https://github.com/FrankKai/webpack-plugin-demo
- 什么是插件
- 新建一个插件
- 插件的基础架构
- compiler和compilation
- 异步事件hook
- tapAsync
- 示例
什么是插件
插件是webpack引擎开放给第三方开发者的一种潜力机制。使用分阶段构建callback,开发者可以引进自己的行为到webpack构建进程。比起构建loader,构建插件更加高级一些,因为你需要理解一些webpack底层内部的hook。做好看源码的准备!
新建一个插件
一个webpack插件,由以下这些内容组成:
- 一个具名js函数或者js类
- 在原型链上定义一个apply方法
- 声明接入的事件hook
- 修改webpack内部实例指定数据
- 在函数完成后,调用webpack提供的callback
class MyExampleWebpackPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync(
'MyExampleWebpackPlugin',
(compilation, callback) => {
console.log('This is an example plugin!');
console.log(Here’s the `compilation` object which represents a single build of assets:', compilation);
compilation.addModule(/* ... */);
callback();
}
)
}
}
插件的基础架构
插件是原型链上有apply方法的实例化对象。这个apply方法会在webpack编译器安装插件时,调用一遍。 apply方法暴露出了webpack底层编译器的引用,可以赋权给编译器callback。
一个插件的结构如下:
class HelloWorldPlugin {
apply(compiler) {
compiler.hooks.done.tap(
'Hello World Plugin',
(
stats /* stats is passed as an argument when done hook is tapped. */
) => {
console.log('Hello World!');
}
);
}
}
module.exports = HelloWorldPlugin;
为了使用这个插件,需要在你的webpack插件配置的plugins增加一个实例。
// webpack.config.js
var HelloWorldPlugin = require('hello-world');
module.exports = {
// ... configuration settings here ...
plugins: [new HelloWorldPlugin({ options: true })],
};
可以通过schema-utils约束webpack插件配置的传参:
import { validate } from 'schema-utils';
// schema for options object
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
开发插件最重要的2个对象:compiler对象,compilation对象。
理解它们是扩展webpack引擎的最重要的一步。
- compiler的hooks中包含了compilation:compiler.hooks.compilation
- compilation也有自己的hook:compilation.hooks.optimize
class HelloCompilationPlugin {
apply(compiler) {
// Tap into compilation hook which gives compilation as argument to the callback function
compiler.hooks.compilation.tap('HelloCompilationPlugin', (compilation) => {
// Now we can tap into various hooks available through compilation
compilation.hooks.optimize.tap('HelloCompilationPlugin', () => {
console.log('Assets are being optimized.');
});
});
}
}
module.exports = HelloCompilationPlugin;
更多compiler和compilation的hook,可以看https://webpack.js.org/api/plugins/
异步事件hook
有一些插件hook是异步的。我们可以使用tabAsync或者tabPromise去同步的使用tap。
tabAsync
当我们使用tabAsync方法去接进插件时,我们需要调用回调函数抛出来的callback函数。
class HelloAsyncPlugin {
apply(compiler) {
compiler.hooks.emit.tabAsync('HelloAsyncPlugin', (compilation, callback) => {
setTimeout(function () {
console.log('Done with async work...');
callback();
}, 1000);
})
}
}
module.exports = HelloAsyncPlugin;
tabPromise
使用tabPromise也可以处理异步tap,需要返回一个promise。
class HelloAsyncPlugin {
apply(compiler) {
compiler.hooks.emit.tapPromise('HelloAsyncPlugin', (compilation) => {
// return a Promise that resolves when we are done...
return new Promise((resolve, reject) => {
setTimeout(function () {
console.log('Done with async work...');
resolve();
}, 1000);
});
});
}
}
module.exports = HelloAsyncPlugin;
示例
了解了webpack compiler以及每个独立的编译步骤后,我们可以为引擎做的事,是无穷大的。我们可以重新格式化已经存在的文件,创建派生文件,或者生产全新的资源。
我们现在创建一个生成assets.md的文件,生成我们每次构建产生的静态文件。
class FileListPlugin {
static defaultOptions = {
outputFile: "assets.md",
};
// 所有的options都要传到插件的constructor
// (这是你的插件的公共API部分)
constructor(options = {}) {
// 用户定义的options覆盖默认options
// 与默认options合并,从而给插件的method使用
// 你需要在这里验证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用来在编译时代表资源class
const { RawSource } = webpack.sources;
// 使用thisCompilation hook使得构建处于较早的阶段
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
// 在指定阶段进入到静态资源的流水线
compilation.hooks.processAssets.tap(
{
name: pluginName,
// 靠后一点的阶段,确保所有资源能被加入到插件的编译中
stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
},
(assets) => {
// assets是包含所有静态资源的对象
// 在编译阶段,对象的键是assets的路径,对象的值是assets的文件内容
// 遍历所有静态文件,生成markdown文件
const content =
"# In this build:\n\n" +
Object.keys(assets)
.map((filename) => `- ${filename}`)
.join("\n");
// 把资源添加给compilation,从而自动由webpack生成到目标目录
compilation.emitAsset(
this.options.outputFile,
new RawSource(content)
);
}
);
});
}
}
module.exports = { FileListPlugin };