插件向第三方开放了 Webpack
引擎中完整的能力,它比 loader
能做更多的事情,这些构建的回调完整的构成了一个 Webpack plugin
的存在,因此我们需要理解一下 Webpack
底层内部的特性来做相应的钩子,这是一件很有趣的事情,学习完我们将用插件来解决一个实际性的问题。
那么一个 Webpack
插件有哪些方面组成:
- 一个命名函数
- 在函数的
prototype
上定义一个apply
方法 - 指定一个 webpack hook
- 处理 Webpack 内部实例特定的数据
- 完成后调用 Webpack 提供的回调
这是我们开发的插件要经历过的固定顺序。在此之前,我们还需要稍微理解一下 Compiler
和 Compilation
,因为apple
会被 Webpack Compiler 调用,并且 compiler 对象会贯穿整个编译生命周期;
compiler
对象代表了完整的 Webpack 环境配置,这个对象在启动时被 Webpack 一次性创建,并且配置好可操作的配置,如:loader
,options
等。compilation
对象代表了资源版本构建,它可以理解为每当 webpack 构建启动时,检测到一个文件的变化,就会创建一个compilation
,从而生成了一组可编译的资源。
说了这么多其实创建一个 Webpack plugin
很简单,只要你能按照上述的顺序创建一个函数,并且理解它的 Compiler
,我们能很快的实现一个插件。
案例
前一段时间在做 Chrome Extension 的国际化时就遇到了一些开发体验上的问题,我们知道 Chrome Extension 的国际化是在 _locales
中创建多个语种的目录和文件,它非常的分散,并且不利于维护,在做的过程中,就想到了 Webpack plugin
来解决这个问题,在预期的设计中,我希望国际化的配置文件能统一维护和处理,它的格式可能是这样的:
// i18n.json
{
"README": {
"zh_CN": "中文描述",
"en": "README"
}
}
转换的目录格式:
|- _locales
|- en
| messages.json
|- zh_CN
| messages.json
en/messages.json
:
{
"README": {
"message": "README"
}
}
zh_CN/messages.json
:
{
"README": {
"message": "中文描述"
}
}
它在使用的过程中,可能需要传递两个参数,如:file
和 i18ns
,这两个参数用于指定文件(这只是设计问题和本身插件的实现无关)。
new chromeExtensionI18nPlugin({
i18ns: ['en', 'zh_CN'],
file: './i18n.json'
})
接下来我们来创建一个 ChromeExtensionI18nPlugin
类,并且实现 apply
,在 apply
方法中再去处理 Webpack Hook
:
const I18N_OUTPUT_DIR = '_locales';
class ChromeExtensionI18nPlugin {
constructor(options) {
this.options = extend({
file: '',
i18ns: [],
spaceNum: 4,
}, options);
}
apply(compiler) {
... // 处理完各种错误
// 使用 compiler.hook 来异步处理
compiler.hooks.emit.tapAsync('ChromeExtensionI18nPlugin', (compilation, callback) => {
// node.js fs 模块
readFileAsync(file, 'utf8').then((source) => {
// 处理数据格式
...
return result;
}).then((result) => {
// 每一个 compilation 代表了一次的资源构建
// 处理构建资源
...
compilation.assets[`${I18N_OUTPUT_DIR}/${i18n}/messages.json`] = {
source() {
return format(result[i18n], spaceNum);
},
size() {
return format(result[i18n], spaceNum).length;
}
}
callback();
})
}
}
}
结语
用一个国际化简单的例子来展示如何创建一个 webpack plugin
,这是一个很有用案例,也解决了我的实际问题,虽然它的构造很简单,当我们比较理解 webpack 的实际结构时,我们可以利用插件来处理更复杂的问题。