編寫一個插件

插件向第三方開發者提供了 webpack 引擎中完整的能力。使用階段式的構建回調,開發者能夠引入它們本身的行爲到 webpack 構建流程中。建立插件比建立 loader 更加高級,由於你將須要理解一些 webpack 底層的內部特性來作相應的鉤子,因此作好閱讀一些源碼的準備!javascript

建立插件

webpack 插件由如下組成:java

  • 一個 JavaScript 命名函數。
  • 在插件函數的 prototype 上定義一個 apply 方法。
  • 指定一個綁定到 webpack 自身的事件鉤子
  • 處理 webpack 內部實例的特定數據。
  • 功能完成後調用 webpack 提供的回調。
// 一個 JavaScript 命名函數。 function MyExampleWebpackPlugin() { }; // 在插件函數的 prototype 上定義一個 `apply` 方法。 MyExampleWebpackPlugin.prototype.apply = function(compiler) { // 指定一個掛載到 webpack 自身的事件鉤子。 compiler.plugin('webpacksEventHook', function(compilation /* 處理 webpack 內部實例的特定數據。*/, callback) { console.log("This is an example plugin!!!"); // 功能完成後調用 webpack 提供的回調。 callback(); }); }; 

Compiler 和 Compilation

在插件開發中最重要的兩個資源就是 compiler 和 compilation 對象。理解它們的角色是擴展 webpack 引擎重要的第一步。webpack

  • compiler 對象表明了完整的 webpack 環境配置。這個對象在啓動 webpack 時被一次性創建,並配置好全部可操做的設置,包括 options,loader 和 plugin。當在 webpack 環境中應用一個插件時,插件將收到此 compiler 對象的引用。可使用它來訪問 webpack 的主環境。git

  • compilation 對象表明了一次資源版本構建。當運行 webpack 開發環境中間件時,每當檢測到一個文件變化,就會建立一個新的 compilation,從而生成一組新的編譯資源。一個 compilation 對象表現了當前的模塊資源、編譯生成資源、變化的文件、以及被跟蹤依賴的狀態信息。compilation 對象也提供了不少關鍵時機的回調,以供插件作自定義處理時選擇使用。github

這兩個組件是任何 webpack 插件不可或缺的部分(特別是 compilation),所以,開發者在閱讀源碼,並熟悉它們以後,會感到獲益匪淺:web

基本插件架構

插件是由「具備 apply 方法的 prototype 對象」所實例化出來的。這個 apply 方法在安裝插件時,會被 webpack compiler 調用一次。apply 方法能夠接收一個 webpack compiler 對象的引用,從而能夠在回調函數中訪問到 compiler 對象。一個簡單的插件結構以下:api

function HelloWorldPlugin(options) { // 使用 options 設置插件實例…… } HelloWorldPlugin.prototype.apply = function(compiler) { compiler.plugin('done', function() { console.log('Hello World!'); }); }; module.exports = HelloWorldPlugin; 

而後,要安裝這個插件,只須要在你的 webpack 配置的 plugin 數組中添加一個實例:數組

var HelloWorldPlugin = require('hello-world'); var webpackConfig = { // ... 這裏是其餘配置 ... plugins: [ new HelloWorldPlugin({options: true}) ] }; 

訪問 compilation 對象

使用 compiler 對象時,你能夠綁定提供了編譯 compilation 引用的回調函數,而後拿到每次新的 compilation 對象。這些 compilation 對象提供了一些鉤子函數,來鉤入到構建流程的不少步驟中。架構

function HelloCompilationPlugin(options) {} HelloCompilationPlugin.prototype.apply = function(compiler) { // 設置回調來訪問 compilation 對象: compiler.plugin("compilation", function(compilation) { // 如今,設置回調來訪問 compilation 中的步驟: compilation.plugin("optimize", function() { console.log("Assets are being optimized."); }); }); }; module.exports = HelloCompilationPlugin; 

關於 compilercompilation 的可用回調,和其它重要的對象的更多信息,請查看 插件 文檔。app

異步編譯插件

有一些編譯插件中的步驟是異步的,這樣就須要額外傳入一個 callback 回調函數,而且在插件運行結束時,_必須_調用這個回調函數。

function HelloAsyncPlugin(options) {} HelloAsyncPlugin.prototype.apply = function(compiler) { compiler.plugin("emit", function(compilation, callback) { // 作一些異步處理…… setTimeout(function() { console.log("Done with async work..."); callback(); }, 1000); }); }; module.exports = HelloAsyncPlugin; 

示例

一旦能咱們深刻理解 webpack compiler 和每一個獨立的 compilation,咱們依賴 webpack 引擎將有無限多的事能夠作。咱們能夠從新格式化已有的文件,建立衍生的文件,或者製做全新的生成文件。

讓咱們來寫一個簡單的示例插件,生成一個叫作 filelist.md 的新文件;文件內容是全部構建生成的文件的列表。這個插件大概像下面這樣:

function FileListPlugin(options) {} FileListPlugin.prototype.apply = function(compiler) { compiler.plugin('emit', function(compilation, callback) { // 在生成文件中,建立一個頭部字符串: var filelist = 'In this build:\n\n'; // 遍歷全部編譯過的資源文件, // 對於每一個文件名稱,都添加一行內容。 for (var filename in compilation.assets) { filelist += ('- '+ filename +'\n'); } // 將這個列表做爲一個新的文件資源,插入到 webpack 構建中: compilation.assets['filelist.md'] = { source: function() { return filelist; }, size: function() { return filelist.length; } }; callback(); }); }; module.exports = FileListPlugin; 

插件的不一樣類型

webpack 插件能夠按照它所註冊的事件分紅不一樣的類型。每個事件鉤子決定了它該如何應用插件的註冊。

  • 同步(synchronous) Tapable 實例應用插件時會使用:

applyPlugins(name: string, args: any...)

applyPluginsBailResult(name: string, args: any...)

這意味着每一個插件回調,都會被特定的 args 一個接一個地調用。 這是插件的最基本形式。許多有用的事件(例如 "compile""this-compilation"),預期插件會同步執行。

  • 瀑布流(waterfall) 插件應用時會使用:

applyPluginsWaterfall(name: string, init: any, args: any...)

這種類型,每一個插件都在其餘插件依次調用以後調用,前一個插件調用的返回值,做爲參數傳入後一個插件。這類插件必須考慮其執行順序。 必須等前一個插件執行後,才能接收參數。第一個插件的值是初始值(init)。這個模式用在與 webpack 模板相關的 Tapable 實例中(例如 ModuleTemplateChunkTemplate 等)。

  • 異步(asynchronous) When all the plugins are applied asynchronously using

applyPluginsAsync(name: string, args: any..., callback: (err?: Error) -> void)

這種類型,插件處理函數在調用時,會傳入全部的參數和一個簽名爲 (err?: Error) -> void 的回調函數。處理函數按註冊時的順序調用。在調用完全部處理程序後,纔會調用 callback。 這也是 "emit""run" 等事件的經常使用模式。

  • 異步瀑布流(async waterfall) 插件將以瀑布方式異步應用。

applyPluginsAsyncWaterfall(name: string, init: any, callback: (err: Error, result: any) -> void)

這種類型,插件處理函數在調用時,會傳入當前值(current value)和一個帶有簽名爲 (err: Error, nextValue: any) -> void. 的回調函數。當調用的 nextValue 是下一個處理函數的當前值(current value)時,第一個處理程序的當前值是 init。在調用完全部處理函數以後,纔會調用 callback,並將最後一個值傳入。若是其中任何一個處理函數傳入一個 err 值,則會調用此 callback 並將此 error 對象傳入,而且再也不調用其餘處理函數。 這種插件模式適用於像 "before-resolve" 和 "after-resolve" 這樣的事件。

  • 異步串聯(async series) 它與異步(asynchronous)相同,但若是任何插件註冊失敗,則再也不調用其餘插件。

applyPluginsAsyncSeries(name: string, args: any..., callback: (err: Error, result: any) -> void)

-並行(parallel) -

applyPluginsParallel(name: string, args: any..., callback: (err?: Error) -> void)

applyPluginsParallelBailResult(name: string, args: any..., callback: (err: Error, result: any) -> void)

相關文章
相關標籤/搜索