webpack
你們應該都耳熟能詳了。我的感受,webpack
的本質就是讓一堆的 Loader
和 Plugin
在webpack
的可支配範圍內,有序可控的執行,最終生成一堆可在瀏覽器中執行的 code 和 一些狀態信息。而這些 Loader
和 Plugin
,有用戶自定義的,也有webpack 本身內部定義的。javascript
Loader
的運行機制,不是這篇文章講述的內容,有須要的朋友,能夠看下我以前的這篇文章:webpack之 loader。html
webpack
的設計思想仍是很好的,我以爲這個思想和漸進加強有殊途同歸之妙。它本身實現一套打包的主流程,而後在 Complier
和 Complation
對象上暴露出一些鉤子,這些鉤子起到了相似於生命週期
的做用,容許用戶在不一樣的打包階段經過鉤子來增長用戶所需的各類各樣的功能。webpack
聰明的將一些不肯定因素拋給使用者去處理,也就是:標準我來定,細節和擴展你本身弄。java
舉個栗子,Complier
對象上的鉤子,就有這麼多 compiler鉤子node
那麼這些鉤子是什麼呢?它們其實都是 tapable
的實例。webpack
那麼咱們怎麼調用這些鉤子呢?經過編寫 webpack Plugin
。git
今天咱們來分析下 webpack
插件機制的基石 - tapable
。考慮到 wepback4
,是使用 tapable
的 1.1.0
版本,因此這篇文章就用 1.1.0
的版原本分析。github
注意:
1.x.x
版本和0.x.x
版本,使用方式是有區別的,代碼也被重構過。web
若是對 tapable
還未有了解的朋友,能夠參考下這裏:npm
tapable@v1.1.0segmentfault
我以爲tapable
整個庫其實就是一套 發佈訂閱模式
的實現,相似 nodejs 的 EventEmitter
。
有的道友說是生產者消費者模式
, 發佈訂閱模式本質上也是一種生產者消費者模式,至於他們的區別,應該就是發佈訂閱模式的功能更單一,而生產者消費者模式的抽象級別更高。究竟是 發佈訂閱模式
仍是 生產者消費者模式
,我不能肯定,有待考究,聰明的你若是知道的話,能夠評論告訴下我哦。
tapable
支持三種方式註冊插件名稱 ,分別是 tap
, tapAsync
, tapPromise
。
tap
表示使用的同步鉤子,tapAsync
和 tapPromise
表示使用的是異步鉤子。
與此對應的,支持三種調用方式 call
, callAsync
,promise
,注意須要一一對應。
用 SyncHook
舉個栗子
const { SyncHook } = require('tapable');
// 初始化時傳入參數名稱
const myHook = new SyncHook(['name', 'age']);
// 添加事件
myHook.tap('pluginName1', (name, age) => console.log('pluginName1', name, age))
myHook.tap('pluginName2', (name, age) => console.log('pluginName2', name, age))
// 觸發
myHook.call('jk', 26);
// 輸出
// pluginName1 jk 26
// pluginName2 jk 26
複製代碼
有木有發現和咱們使用 addEventListener
添加事件很是類似?是的,就是這麼像。因此不要怕它,咱們只須要注意, 聲明 Hook
時,傳入預置的參數名稱,而後用 tap
監聽事件,用 call
傳入參數觸發事件。
用起來和 jQuery
的 on
與 trigger
一個意思,固然內部處理流程是不同。
這裏咱們用的是 SyncHook
,還有 Async
類型的 Hook
,也就是異步鉤子,用起來也是相似的。
tapAsync
監聽,callAsync
觸發
tapPromise
監聽,promise
觸發
代碼以下:
const { AsyncParallelHook } = require("tapable");
class Model {
constructor() {
this.hooks = {
asyncHook: new AsyncParallelHook(['name']),
promiseHook: new AsyncParallelHook(['age'])
};
}
callAsyncHook(name, callback) {
this.hooks.asyncHook.callAsync(name, err => {
if (err) return callback(err);
callback(null);
});
}
callPromiseHook(age) {
return this.hooks.promiseHook.promise(age).then(res => console.log(res));
}
}
const model = new Model();
// Async 方式監聽事件
model.hooks.asyncHook.tapAsync('AsyncPluginName', (name, callback) => {
const pluginName = 'AsyncPluginName';
setTimeout( () => {
console.log(pluginName, name);
}, 2000);
});
model.callAsyncHook('jk');
// 2秒後輸出:AsyncPluginName jk
// Promise 方式監聽事件
model.hooks.promiseHook.tapPromise('PromisePluginName', (age) => {
const pluginName = 'PromisePluginName';
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(pluginName, age);
resolve(pluginName);
}, 4000);
});
});
model.callPromiseHook(26);
// 4秒後輸出:PromisePluginName 26
複製代碼
其餘的類型,可參考官方文檔
const pluginName = 'HelloWorldPlugin';
class HelloWorldPlugin {
apply(complier) {
compiler.hooks.someHook.tapAsync(
pluginName,
(compilation, callback) => {
// Do something async...
setTimeout( () => {
console.log('Done with async work...');
callback();
}, 1000);
}
)
}
}
module.exports = HelloWorldPlugin;
複製代碼
由於 webpack 在初始化 時,會遍歷 plugins
參數中的實例,依次調用實例的 apply
方法,並將 complier
做爲參數。
源碼出自: webpack\lib\webpack.js
到這裏應該能明白,爲何 webpack
的插件須要按照那樣的格式去寫了。
目錄結構如圖
MyPlugin.js 代碼以下,我這裏是直接 copy HTMLWebpackPlugin
官方文檔的。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const pluginName = 'MyPlugin';
class MyPlugin {
apply(compiler) {
compiler.hooks.compilation.tap(pluginName, (compilation) => {
console.log('The compiler is starting a new compilation...')
// Staic Plugin interface |compilation |HOOK NAME | register listener
HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync(
pluginName, // <-- Set a meaningful name here for stacktraces
(data, cb) => {
// Manipulate the content
data.html += 'The Magic Footer'
// Tell webpack to move on
cb(null, data)
}
)
})
}
}
module.exports = MyPlugin;
複製代碼
注意一點:HTMLWebpackPlugin
默認安裝 3.2.0
版本,這個版本還只是支持舊版的插件寫法,沒有 HtmlWebpackPlugin.getHooks
這個方法。我這裏安裝的是最新的master分支。
一切就緒後,npm run prod
,可看到 dist/index.html
的內容以下:
一個簡單的插件算是寫好並運行成功了。
但願本文能對讀者有幫助。
若是有錯誤的地方,還請指出。
謝謝閱讀。
代碼在此:webpack-plugin-test