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' }) ] }