如何開發webpack plugin

瀟湘待雨發表於2017-10-20

繼上回介紹了如何開發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檢視。
另外,對於我這種英語渣渣來說,翻譯起來確實難度蠻大的。此處拋磚引玉,希望大家共同探討學習。

相關文章