自定义webpack插件
插件向第三方开发者提供了webpack
引擎中完整的能力。使用阶段式的构建回调,开发者可以在webpack
构建流程中引入自定义的行。
创建插件比创建loader
更高级,因为你需要理解webpack
底层的特性来处理相应的钩子。
1.创建插件
webpack
插件由以下组成:
- 一个
JavaScript
命名函数或者JavaScript
类 - 在插件函数的
prototype
上定义一个apply
方法 - 指定一个绑定到
webpack
自身的事件钩子 - 处理
webpack
内部实例的特定数据 - 功能完成后调用
webpack
提供的回调
代码如下:
// JS类
class MyWebpackPlugin {
// 必须包含一个apply方法,参数为compiler对象
apply(compiler) {
// 指定一个挂载到webpack自身的事件钩子
compiler.hooks.emit.tapAsync(
'MyWebpackPlugin',
// 单次构建的compilation对象以及回调函数
(compilation, callback) => {
console.log('it is a example plugin');
// 用webpack提供的插件api处理构建过程
compilation.addModule();
callback();
}
)
}
}
2.基本插件架构
插件是由具有apply
方法的prototype
对象所实例化而来。这个apply
方法在安装插件时,会被webpack compiler
调用一次。
apply
方法接收一个webpack compiler
对象的引用,从而可以在回调函数中访问compiler
对象。一个插件结构如下:
class ExamplePlugin {
apply(compiler) {
compiler.hooks.done.tap(
'ExamplePlugin',
(stats => {
console.log('hello, world');
})
)
}
}
module.exports = ExamplePlugin;
然后,要安装这个插件,只需要在webpack
的配置中引入,在Plugins
数组内,添加这个插件的实例即可
const ExamplePlugin = require('ExamplePlugin');
module.exports = {
// 其他配置
plugins: [
new ExamplePlugin({options})
]
}
另外,可以使用schma-utils来校验传入插件的选项,比如
import { validate } from 'schma-utils';
const options = {
type: 'object',
properties: {
test: {
type: 'string'
}
}
};
export default class ExamplePlugin {
constructor(options = {}) {
validate(schma, options, {
name: 'hello, world',
bashDataPath: 'options',
})
}
apply(compiler) {}
}
3.compiler和compilation
在插件开发过程中有2个非常重要的资源就是compiler
和compilation
对象。理解它们是深入Plugin
重要的一步
export default class ExamplePlugin {
apply(compiler) {
// 指定一个挂载到compilation的钩子函数,回调函数的参数就是compilation
compiler.hooks.compilation.tap(
'ExamplePlugin',
(compilation) => {
// 现在可以通过compilation对象绑定各种钩子函数了
compilation.hooks.optimize.tap(
'ExamplePlugin',
() => {
console.log('资源已经优化完毕');
}
)
}
)
}
}
更多关于compiler
和compilation
的钩子函数,请查看Plugins API
异步编译插件
有些插件钩子是异步的。我们可以像同步一样用tap
方法来绑定,也可以使用tapAsync
或者tapPromise
这2个异步方法来绑定。
tapAsync
当我们使用tapAsync
方法来绑定插件时,必须调用函数的最后一个参数callback
指定的回调函数,否则webpack
编译会出问题
export default class ExamplePlugin {
apply(compiler) {
// 指定一个挂载到compilation的钩子函数,回调函数的参数就是compilation
compiler.hooks.compilation.tapAsync(
'ExamplePlugin',
(compilation, callback) => {
setTimeout(()=>{
// 模拟异步操作
// 完毕后调用callback函数,继续执行后续代码
callback()
}, 2000)
}
)
}
}
tapPromise
当我们用tapPromise
方法来绑定插件时,必须返回一个Promise
,异步任务完成后调用resolve
方法。
export default class ExamplePlugin {
apply(compiler) {
// 指定一个挂载到compilation的钩子函数,回调函数的参数就是compilation
compiler.hooks.compilation.tapPromise(
'ExamplePlugin',
(compilation) => {
return new Promise((resolve, reject) => {
setTimeout(()=>{
// 模拟异步操作
// 完毕后调用callback函数,继续执行后续代码
resolve()
}, 2000)
})
}
)
}
}
完整例子
看到这里,我们对webpack
的compiler
以及每个独立的compilation
有了深入理解,那么我们可以在这期间做很多事情。我们可以格式化已有的文件,创建衍生的文件,或者生成全新的生成文件等。
让我们来写一个简单的插件官方示例,生成一个叫做assets.md
的新文件,文件内容就是所有构建生成文件的列表。这个插件大概长这样:
目前发现官方示例已经跑不起来了,各种报错:
compilation
里已经没有webpack
了RawSource
需要在webpack-sources
里引入compilation
上也没有processAssets
的hooks
了,尽管官网上有,但是目前代码看不到了。另外我也发现afterProcessAssets
也没有了
具体可以参见: compiler hooks, compilation对象, compilation hooks
const RawSource = require('webpack-sources').RawSource;
// 这是一个webpack的插件
class FileListPlugin {
// 这里去处理插件传进来的一些参数,并和默认参数进行合并,生成统一的插件参数
constructor(params) {
// 默认的文件名就叫assets.md
const defaultOptions = {
outputName: 'assets.md',
}
this.options = Object.assign({}, defaultOptions, params);
}
/*
apply函数是插件的灵魂,必须存在
入参是compiler对象,这个对象方法非常多,
提供了一些钩子函数,方便我们在webpack各个生命周期里做很多事情
*/
apply(compiler) {
const pluginName = FileListPlugin.name;
/*
绑定到thisCompilation钩子
这样可以进一步绑定到Compilation过程更早期的阶段
*/
compiler.hooks.thisCompilation.tap(pluginName, (compilation) => {
// 绑定到资源处理流水线(assets processing pipeline)
compilation.hooks.optimizeAssets.tap(
{
name: pluginName
},
/*
assets是一个包含compilation中所有资源(assets)的对象。
该对象的键是自愿的路径
值是资源的源码
*/
(assets) => {
// 遍历所有资源,生成md文件
const files = Object.keys(assets)
.map(fileName => '1. ' + fileName)
.join('\r\n');
const content = `## here is ${pluginName}.md\r\n\r\n` + files;
/*
向compilation添加新的资源
这样webpack就会自动生成并输出到output目录
*/
compilation.emitAsset(
this.options.outputName,
new RawSource(content)
)})
// compilation对象上完成的方法,参数是一个函数
compilation.finish(() => {
console.log("ok, it's finished!!!");
})
})
}
};
module.exports = FileListPlugin;
在webpack.config.js
引入之后,进行编译,会发现生成了一个assets.md
文件,里面的内容是:
## here is FileListPlugin.md
1. images/1.png
1. 087ec4ede1fe38a9e4838bee53b7dc1a.css
1. index.js