plugin是webpack生态的重要组成,它为用户提供了一种可以直接访问到webpack编译过程的方式。它可以访问到编译过程触发的所有关键事件。
1. 基本概念
1. 如何实现一个插件
1. plugin实际是一个类(构造函数),通过在plugins配置中实例化进行调用。
// webpack.config.js var MyExampleWebpackPlugin = require('my-example-webpack-plugin'); module.exports = { // ... 这里是其他配置 ... plugins: [new MyExampleWebpackPlugin({ options: xxx })] };
2. 它在原型对象上指定了一个apply方法,入参是compiler对象
3. 指定一个事件钩子,并调用内部提供的API
4. 完成操作后,调用webpack 提供的callback方法
// 一个 JavaScript class class MyExampleWebpackPlugin { // 将 `apply` 定义为其原型方法,此方法以 compiler 作为参数 apply(compiler) { // 指定要附加到的事件钩子函数 compiler.hooks.emit.tapAsync('MyExampleWebpackPlugin', (compilation, callback) => {// 使用 webpack 提供的 plugin API 操作构建结果 compilation.addModule(/* ... */); callback(); } ); } }
2. 实现插件的背景知识
由上面的步骤可知,插件功能的实现主要依赖于compiler和complation对象,而两者都是继承自Tapable对象。它暴露三种注册监听的方法Tapable对象主要是9种钩子:
const {
SyncHook,
SyncBailHook,
SyncWaterfallHook,
SyncLoopHook,
AsyncParallelHook,
AsyncParallelBailHook,
AsyncSeriesHook,
AsyncSeriesBailHook,
AsyncSeriesWaterfallHook
} = require("tapable");
其中同步四种,异步并行两种,异步串行3种。
同步钩子进行同步操作;异步钩子中进行异步操作。
compiler和compilation中的钩子都是自称自这9种钩子。钩子的工作机制类似于浏览器的事件监听。
1)生成的钩子可以注册监听事件,其中同步钩子通过tap方法监听,异步钩子通过tapAsync(+回调函数)和tapPromise(+返回promise)进行监听。
2)还可以进行拦截,通过intercept方法。
3)对于监听事件的触发,同步钩子通过call方法; 异步钩子通过callAsync方法和promise
示例1: -SyncHook
监听事件都是按照注册的顺序依次执行
const { SyncHook } = require('tapable'); const hook = new SyncHook(['name', 'age']); hook.tap('任意字符1', (name, age) => { console.log(1); return undefined; }) hook.tap('任意字符2', (name, age) => { console.log(2);
return true; }) hook.tap('任意字符3', (name, age) => { console.log(3); }) hook.call('lyra', 18); //传入的参数必须和初始化实例时传入的参数个数相同 // 执行顺序如下
1
2
3
示例2: -SyncBailHook
只要返回非undefined值,终止监听事件的调用
示例3:-SyncWaterfallHook
监听事件如果返回非undefined值,作为下个监听事件的第一个参数;如果返回undefined,返回之前的监听事件中最近的非undefined值
const { SyncWaterfallHook } = require('tapable'); const hook = new SyncWaterfallHook(['name', 'age']); hook.tap('任意字符1', (name, age) => { console.log('1-->',name,age); return 19; }) hook.tap('任意字符2', (name, age) => { console.log('2-->',name,age); return undefined; }) hook.tap('任意字符3', (name, age) => { console.log('3-->',name,age); return 21; }) hook.call('lyra', 18); //传入的参数必须和初始化实例时传入的参数个数相同 // 执行顺序如下 1--> lyra 18 2--> 19 18 3--> 19 18
示例4: -SyncLoopHook
只要监听事件返回的是非undefined值,则回到该钩子的第一个监听事件从头开始执行
示例5: -AsyncSeriesHook(complier.hooks.emit)
串行异步会等前一个事件结束再执行第二个监听事件
// 串行异步,则耗时需要3s const { AsyncSeriesHook } = require('tapable'); const hook = new AsyncSeriesHook(['name', 'age']); hook.tapPromise('任意字符1', (name, age) => { // 因为使用的是tapPromise监听,必须返回一个promise console.time(1) return new Promise(function(resolve, reject){ setTimeout(function() { resolve(1); }, 1000) }) }) hook.tapPromise('任意字符2', (name, age) => { return new Promise((resolve, reject) => { setTimeout(function() { resolve(1); console.timeEnd(1) }, 2000) }); })
// 通过promise方法触发监听 hook.promise('lyra', 18).then(() => { // TODO });
示例6: - AsyncParallelHook(complier.hooks.make)
并行异步,所有监听函数同时执行
// 并行异步,耗时2秒,其实就是所有监听事件中耗时最长的事件 const { AsyncParallelHook } = require('tapable'); const hook = new AsyncParallelHook(['name', 'age']); hook.tapAsync('任意字符1', (name, age, callback) => { // 通过tapAsync方法注册监听;通过callback方法完成监听 console.time(1) setTimeout(() => { callback(); },1000) }) hook.tapAsync('任意字符2', (name, age, callback) => { setTimeout(() => { callback(); console.timeEnd(1) }, 2000) }) // 通过callAsync方法触发监听;且必须有回调函数 hook.callAsync('lyra', 18, function(e) { //该函数必须存在 });
2. 自定义创建插件
1. 打包zip插件
const JsZip = require('jszip'); class ZipPlugin { constructor(options) { this.options = options; } apply(compiler) { // emit是一个异步串行钩子 compiler.hooks.emit.tapPromise('1', (compilation) => { const assets = compilation.assets; const zip = new JsZip(); for(let filename in assets) { zip.file(filename, assets[filename].source()) } // nodebuffer是node环境中的二进制形式;blob是浏览器环境 return zip.generateAsync({type: 'nodebuffer'}).then((content) =>{ console.log(this.options.name); assets[this.options.name] = { source() {return content}, size() {return content.length} //可以省略 } return new Promise((resolve, reject) => { resolve(compilation) }) }) }) } } module.exports = ZipPlugin;
在webpack.config.js中使用
const ZipPlugin = require('./plugins/ZipPlugin'); module.exports = { plugins: [ new ZipPlugin({ name: 'my.zip' }) ] }