從零實現一個 Webpack Plugin

Plugins expose the full potential of the webpack engine to third-party developers. ----------- Webpackhtml

相比於 loaders,plugin 更加的靈活,由於它可以接觸到 webpack 編譯器和編譯核心。這就使得 plugin 能夠經過一些 hook 函數來攔截 webpack 的執行,甚至你能夠運行一個子編譯器和 loader 串聯,像 MiniCssExtractPlugin 就是這麼作的。webpack

示例代碼:linkgit

webpack plugin 基本結構

html-webpack-plugin 爲例,它的使用以下github

plugins: [
    new HtmlWebpackPlugin({
        ...
    }),
],
複製代碼

不難看出,webpack plugin 的基本形式一個構造函數 new function(),同時爲了可以得到 compiler,就須要 plugin 對外暴露一個接口(爲 apply 函數)。因此,它的基本結構以下:web

  • 一個命名的 JavaScript 函數對象;
  • 在其 prototype 上,定義一個 apply 方法。

JavaScript 的實現這種形式的方法有不少,本文采用 class 來實現,具體以下npm

module.exports = class DemoPlugin {
    apply() {
        console.log('applying')
    }
}
複製代碼

配置開發環境

爲了可以運行這個 plugin,咱們須要建立一個環境json

mkdir webpack-demo-plugin
cd webpack-demo-plugin
npm init
複製代碼

webpack.plugin.jsapi

const path = require("path");

const PATHS = {
  lib: path.join(__dirname, "index.js"),
  build: path.join(__dirname, "build"),
};

module.exports = {
  entry: {
    lib: PATHS.lib,
  },
  output: {
    path: PATHS.build,
    filename: "[name].js",
  },
};
複製代碼

index.jsbash

console.log("hello world")
複製代碼

同時向 package.json 中添加markdown

"scripts": {
    "build:plugin": "webpack --config webpack.plugin.js --mode production",
    ...
  }
複製代碼

實現 webpack demo

建立 plugins/demo-plugin.js 文件,內容爲以前的 webpack plugin demo,並將其引入到 webpack.plugin.js 內。

webpack.plugin.js

const DemoPlugin = require("./plugins/demo-plugin.js");

module.exports = {
  ...
  // 引入 plugin
  plugins: [new DemoPlugin()],
};
複製代碼

嘗試運行下 npm run build:plugin,終端上打印出

applying
Hash: 98c8997160aa995a58a4
Version: webpack 4.12.0
Time: 93ms
Built at: 2019-04-29 14:34:31
 Asset       Size  Chunks             Chunk Names
lib.js  956 bytes       0  [emitted]  lib
[0] ./index.js 26 bytes {0} [built]
複製代碼

驚奇的發現 applying,說明插件已經成功運行。

傳遞參數

在應用一個 plugin 時,有時須要根據傳遞 Options 來告訴 plugin 具體應該作什麼。當 new DemoPlugin() 時候會觸發 class DemoPluginconstructor 因此

plugins/demo-plugin.js

module.exports = class DemoPlugin {
    constructor(options) {
        this.options = options
    }
    apply() {
        console.log('apply', this.options)
    }
}
複製代碼

同時,還須要修改 webpack.plugin.js 來傳遞對應參數

module.exports = {
    ...
    plugins: [new DemoPlugin({ name: 'demo' })],
}
複製代碼

運行 npm run build:plugin,能夠發現 apply { name: 'demo' }。這裏介紹一個經常使用插件 schema-utils 可以用來校驗 Options。

理解 webpack 的 compiler 和 compilation

在以前的 webpack plugin 基本結構中介紹,apply 函數可以用來訪問 webpack 的核心。具體的作法是,apply 函數的參數爲 compiler

plugins/demo-plugin.js

module.exports = class DemoPlugin {
    constructor(options) {
        this.options = options
    }
    apply(compiler) {
        console.log(compiler)
    }
}
複製代碼

再次運行 npm run build:plugin,會發現終端上打印出了 compiler 的所有信息,其中 hooks 字段佔了絕大部分。

對照着官方文檔,你會發現每個 hook 對應一個特定的階段。 例如,emit 實踐是在向輸出目錄發送資源以前執行。這樣就能夠經過監聽 hook 來實現控制編譯過程。

plugins/demo-plugin.js

module.exports = class DemoPlugin {
    constructor(options) {
        this.options = options
    }
    apply(compiler) {
        compiler.plugin('emit', (compilation, next) => {
            console.log(compilation)

            next()
        })
    }
}
複製代碼

不要忘記調用 next,不然 webpack 將不會繼續打包。

運行 npm run build:plugin 會顯示出比之前更多的信息,由於編譯對象包含webpack 遍歷的整個依賴關係圖。 你能夠訪問與此相關的全部內容,包括 entries, chunks, modules, assets等。

經過 Compilation 寫入文件

能夠經過 compilationassets 對象來編寫新的文件,或是修改已經建立的文件。爲了更好地寫入文件,咱們引入一個 npm 包

npm install webpack-sources --save-dev
複製代碼

plugins/demo-plugin.js

const { RawSource } = require("webpack-sources");

module.exports = class DemoPlugin {
    constructor(options) {
        this.options = options
    }
    apply(compiler) {
        const { name } = this.options;

        compiler.plugin('emit', (compilation, next) => {
            compilation.assets[name] = new RawSource("demo");

            next()
        })
    }
}
複製代碼

在終端運行 npm run build:plugin

Hash: 98c8997160aa995a58a4
Version: webpack 4.12.0
Time: 95ms
Built at: 2019-04-29 16:08:52
 Asset       Size  Chunks             Chunk Names
lib.js  956 bytes       0  [emitted]  lib
  demo    4 bytes          [emitted]
[0] ./index.js 26 bytes {0} [built]
複製代碼

在 Asset 那裏一列內,出現了咱們自定的 demo 文件。

更多的鉤子函數,請見 the official compilation reference

管理 Warnings 和 Errors

作一個實驗,若是你在 apply 函數內插入 throw new Error("Message"),會發生什麼,終端會打印出 Unhandled rejection Error: Message。而後 webpack 中斷執行。

爲了避免影響 webpack 的執行,要在編譯期間向用戶發出警告或錯誤消息,則應使用 compilation.warningscompilation.errors

compilation.warnings.push("warning");
compilation.errors.push("error");
複製代碼

總結

當你着手開始設計插件時,必定要花時間研究同類型的現有插件。 逐個開發插件,以便你一次只驗證一個插件。 遇到問題,能夠查看 webpack 源碼,它會加強你的 debug 直覺。

參考:

webpack.js.org/contribute/…

survivejs.com/webpack/ext…

webpack.js.org/api/plugins…

相關文章
相關標籤/搜索