繼上回介紹了如何開發webpack loader 之后。趁熱打鐵,來繼續看下webpack另一個核心組成:plugin。
下面也和loader一樣,讓我們一起從基本的官方文檔着手看起。
loader和plugin的差別
- loader : 顧名思義,某種類型資源文件的加載器,作用於某種類型的文件上。webpack本身也是不能直接打包這些非js文件的,需要一個轉化器即loader。 loader本身是單一,簡單的,不能將多個功能放在一個loader里。
- plugin比loaders更加先進一點,你可以擴展webpack的功能來滿足自己的需要,換句話說,loader不能滿足的時候,就需要plugin了。
如何開發一個plugin
插件將webpack引擎所有的能力暴露給第三方開發者。通過階梯式的build回調,開發者可以在webpack編譯過程中加入自己的行為。開發插件比loaders更加先進一點,因為你需要理解webpack一些底層構成來添加鈎子回調。准備好讀一些源碼吧。
開發一個插件
一個webpack的插件由以下幾方面組成:
- 一個非匿名的js函數
- 在它的原型對象上定義apply方法
- 指明掛載自身的webpack鈎子事件
- 操作webpack內部情況的特定數據
- 方法完成時喚起webpack提供的回調
// A named JavaScript function.
function MyExampleWebpackPlugin() {
//
};
// Defines `apply` method in it's prototype.
MyExampleWebpackPlugin.prototype.apply = function(compiler) {
// Specifies webpack's event hook to attach itself.
compiler.plugin('webpacksEventHook', function(compilation /* Manipulates webpack internal instance specific data. */, callback) {
console.log("This is an example plugin!!!");
// Invokes webpack provided callback after functionality is complete.
callback();
});
};
編譯器和編譯
開發插件過程中最重要的兩個對象就是compiler 和compilation。理解他們的職責是擴展webpack功能最重要的第一步
編譯器對象就是webpack完整的配置環境。該對象一經webpack開始執行就創建,並且通過所有可操作的設置項來設置,例如options,loaders,和plugins。當在webpack環境中應用一個插件時,該插件將會接受到一個指向該編譯器的引用。使用該編譯器來訪問主要的webpack環境。
compilation對象是一個單獨的關於版本資源的創建。當執行webpack 開發中間件時,當一個文件的更改被檢測到就會創建一個新的compilation對象,因此產生了一些可被編譯的資源。一個compilation展現了一些信息關於當前模塊資源狀態、編譯資源、改變的文件、監視的依賴等信息。同樣提供了很多關鍵的回調,當插件擴展自定義行為時
這兩個組件是webpack 插件必需的組成部分(特別是compilation),所以開發者如果熟悉下面這些源文件將會獲益不小。
插件的基本結構
插件是在原型中帶有一個apply方法的實例化對象,當安裝插件的時候,這個apply方法就會被webpack調用一次。apply方法提供一個指向當前活動的webpack compiler的引用,該引用允許訪問compiler的回調。一個簡單的插件結構如下:
function HelloWorldPlugin(options) {
// Setup the plugin instance with options...
}
HelloWorldPlugin.prototype.apply = function(compiler) {
compiler.plugin('done', function() {
console.log('Hello World!');
});
};
module.exports = HelloWorldPlugin;
然后安裝一個插件,僅僅需要在你的 webpack config 中plugins對應的數組中,增加一個插件的實例即可
var HelloWorldPlugin = require('hello-world');
var webpackConfig = {
// ... config settings here ...
plugins: [
new HelloWorldPlugin({options: true})
]
};
訪問編譯
通過使用編譯器對象,你可能會綁定提供指向每個新的compilation應用的回調。這些compilations提供了編譯過程中很多步驟的回調函數。
function HelloCompilationPlugin(options) {}
HelloCompilationPlugin.prototype.apply = function(compiler) {
// Setup callback for accessing a compilation:
compiler.plugin("compilation", function(compilation) {
// Now setup callbacks for accessing compilation steps:
compilation.plugin("optimize", function() {
console.log("Assets are being optimized.");
});
});
};
module.exports = HelloCompilationPlugin;
如果想了解更多關於在編譯器、編譯中哪些回調是可用的和其他一些更重要的對象,輕戳plugin文檔
異步插件
一些編譯插件步驟是異步的並且提供了一個當你的插件結束編譯時必須調用的回調方法
function HelloAsyncPlugin(options) {}
HelloAsyncPlugin.prototype.apply = function(compiler) {
compiler.plugin("emit", function(compilation, callback) {
// Do something async...
setTimeout(function() {
console.log("Done with async work...");
callback();
}, 1000);
});
};
module.exports = HelloAsyncPlugin;
示例
一旦我們打開了webpack編譯器和每個單獨編譯的大門,我們可以使用引擎做的事情是無限可能的。我們可以重新格式化存在的文件、創建派生文件、完全偽造一個新文件
讓我們寫個簡單的示例插件,目的是生成一個新的名字為filelist.md的文件。內容如下:列出構建過程中所有的生成文件。這個插件大概如下:
function FileListPlugin(options) {}
FileListPlugin.prototype.apply = function(compiler) {
compiler.plugin('emit', function(compilation, callback) {
// Create a header string for the generated file:
var filelist = 'In this build:\n\n';
// Loop through all compiled assets,
// adding a new line item for each filename.
for (var filename in compilation.assets) {
filelist += ('- '+ filename +'\n');
}
// Insert this list into the webpack build as a new file asset:
compilation.assets['filelist.md'] = {
source: function() {
return filelist;
},
size: function() {
return filelist.length;
}
};
callback();
});
};
module.exports = FileListPlugin;
不同類型的插件
插件可以依據其注冊的事件來分成不同的類型,每個事件鈎子決定了在觸發時如何調用該插件。
同步類型
這種類型的實例使用如下方式來調用插件
applyPlugins(name: string, args: any...)
applyPluginsBailResult(name: string, args: any...)
這意味着每一個插件的回調將伴隨特定參數args依次被調用。對插件而言這是最簡單的格式。很多有用的事件例如"compile", "this-compilation",是期望插件同步執行的。
流式類型
waterfall Plugins 通過下面的方式調用
applyPluginsWaterfall(name: string, init: any, args: any...)
異步類型
當所有的插件被使用下面的方法異步調用的時候,即為異步插件
applyPluginsAsync(name: string, args: any..., callback: (err?: Error) -> void)
插件控制方法被調用,參數是所有的args和帶有這種標志(err?: Error) -> void的回調。handler方法按照注冊回調在所有handlers被調用之后的順序來調用。對於"emit", "run"事件來說這是很常用的模式。
異步流
這種插件將按照流失方式來被異步使用
applyPluginsAsyncWaterfall(name: string, init: any, callback: (err: Error, result: any) -> void)
這種插件的handler被調用時,參數是當前value和帶有這種標志(err?: Error) -> void的回調。當被調用時,nextValue是下一個handler的當前值。第一個handler的當前值是init。所有的handler被調用之后,最后一個值將會被賦給回調。如果有的handler傳遞了一個err的值,回調將會接受err,並且不會有其他handler被第阿勇。這種插件模式使用與於"before-resolve" and "after-resolve"之類的事件。
異步系列
這種和異步插件很相似,不同在於如果有點插件注冊失敗,將不會調用任何插件
applyPluginsAsyncSeries(name: string, args: any..., callback: (err: Error, result: any) -> void)
結束語
至此,如何開發一個基本的webpack plugin 我相信大家已經知道了,如果還不太清楚的話,可以移步w-loader查看。
另外,對於我這種英語渣渣來說,翻譯起來確實難度蠻大的。此處拋磚引玉,希望大家共同探討學習。