如何编写自定义webpack plugin?
2021-05-14 本文已影响0人
努力学习的小丸子
参考文章:https://webpack.docschina.org/api/compiler-hooks/
webpack插件包括:
- 具名function或class
- 原型上有apply方法
- 在特定钩子函数执行代码
- 处理compiler或compilation对象上的数据
- 调用webpack提供的回调
// A JavaScript class.
class MyExampleWebpackPlugin {
// Define `apply` as its prototype method which is supplied with compiler as its argument
apply(compiler) {
// Specify the event hook to attach to
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
);
// Manipulate the build using the plugin API provided by webpack
compilation.addModule(/* ... */);
callback();
}
);
}
}
apply(compiler) {
let skeletons;
// compatible with webpack 4.x
if (compiler.hooks) {
compiler.hooks.make.tapAsync(PLUGIN_NAME, (compilation, cb) => {
if (!compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing) {
console.error('VueSkeletonWebpackPlugin must be placed after HtmlWebpackPlugin in `plugins`.');
return;
}
this.generateSkeletonForEntries(this.extractEntries(compiler.options.entry), compiler, compilation)
.then(skeletonResults => {
skeletons = skeletonResults.reduce((cur, prev) => Object.assign(prev, cur), {});
cb();
})
.catch(e => console.log(e));
compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing.tapAsync(PLUGIN_NAME, (htmlPluginData, callback) => {
this.injectToHtml(htmlPluginData, skeletons);
callback(null, htmlPluginData);
});
});
}
else {
compiler.plugin('make', (compilation, cb) => {
this.generateSkeletonForEntries(this.extractEntries(compiler.options.entry), compiler, compilation)
.then(skeletonResults => {
skeletons = skeletonResults.reduce((cur, prev) => Object.assign(prev, cur), {});
cb();
})
.catch(e => console.log(e));
compilation.plugin('html-webpack-plugin-before-html-processing', (htmlPluginData, callback) => {
this.injectToHtml(htmlPluginData, skeletons);
callback(null, htmlPluginData);
});
});
}
}
compiler所有钩子函数详见:https://webpack.docschina.org/api/compiler-hooks/
compiler钩子函数使用示例:
emit : 生成资源到output之前,在需要更改输出的文件时很有用,比如去掉所有的console.log。
make :compilation 结束之前执行。
compilation :compilation 创建之后执行。
afterPlugins :在初始化内部插件集合完成设置之后调用。
对于初始化插件需传参的情况,在constructor中定义。以下使用了schema-utils来校验传入的参数是否符合规范。
import { validate } from 'schema-utils';
import schema from 'path/to/schema.json';
class Plugin {
constructor(options) {
validate(schema, options, {
name: 'Plugin Name',
baseDataPath: 'options',
});
this.options = options;
}
apply(compiler) {
// Code...
}
}
export default Plugin;
插件调用顺序
插件按照在配置文件中的定义顺序依次执行,并且插件必须调用钩子函数中的cb方法才会执行下一个插件。
比如,自动生成一些js文件。在生成文件之后再执行GenerateRenderIndexPlugin插件。
// generateRenderApiPlugin.js
class GenerateRenderApiPlugin {
apply(compiler) {
compiler.hooks.beforeRun.tapAsync('GenerateRenderApiPlugin', (compilation, cb) => {
// 该插件中需要执行一些异步操作
let actions = [Promise.resolve()]
Promise.all(actions).then(() => {
cb();
}).catch((e) => {
// cb()
})
})
}
}
// webpack.config.js
plugins: [
new GenerateRenderApiPlugin(),
new GenerateRenderIndexPlugin()
]
将字符串变成可执行代码:
vm.runInThisContext(`let a = 1`)
console.log(a);
输出格式化的代码到文件:
const beautify = require('js-beautify').js;
util.promisify(fs.writeFile)(`${outputPath}/${prefix}.js`, beautify(apiContent, { indent_size: 2, space_in_empty_paren: true }), 'utf8').then((data) => {
console.log('test write success')
})
字符串全部替换:
const replaceAll = (target, reg, slot) => {
return target.replace(reg, slot);
}
let reg = new RegExp(/customR/g);
replaceAll(`let {a} = customR('./a.js')`, reg, 'require');
插件传参
const { validate } = require('schema-utils')
const schema = {
type: "object",
properties: {
test: {
type: "string",
},
},
};
class RemoveConsolePlugin {
constructor(options) {
console.log('init plugin');
validate(schema, options, {
name: 'Plugin Name',
baseDataPath: 'options',
});
this.options = options;
}
apply(compiler) {
if (compiler.hooks) {
console.log('webpack 4');
} else {
console.log('webpack 5');
}
}
}
module.exports = RemoveConsolePlugin;