Stories

Detail Return Return

手擼Webpack自定義Plugin - Stories Detail

前言

webpack-plugin 向開發者提供了 webpack 引擎中完整的能力。通過插件擴展 webpack,加入自定義的構建行為,使 webpack 可以執行更廣泛的任務,擁有更強的構建能力。

與 loader 相同,plugin 的本質也是一個模塊(它包含一個apply函數),符合 webpack 的一切皆模塊的理念。

工作原理

webpack 就像一條串行的生產線,要經過一系列處理流程後才能將源文件轉換成輸出結果。在這條生產線上webpack 會把一些關鍵的流程節點暴露給開發者,這些節點稱為 hook(鈎子),可以類比於 Vue 的生命週期鈎子。

webpack-plugin 通過監聽需要關注的 hook,在 hook 中引入自定義的構建行為,就能加入到這條生產線中,去改變生產線的運作。

webpack 通過 Tapable 來組織這條複雜的生產線,Tapable 是 webpack 的核心功能庫,它提供了統一的插件hook(鈎子)類型定義。同時,為 hook 提供了三個方法註冊插件功能:

  • tap:註冊同步鈎子和異步鈎子。
  • tapAsync:回調方式註冊異步鈎子。
  • tapPromise:Promise 方式註冊異步鈎子。

plugin示例

以下是一個官方的 plugin 示例:

// 一個 JavaScript 類
class MyExampleWebpackPlugin {
  // 在插件函數的 prototype 上定義一個 `apply` 方法,以 compiler 為參數。
  apply(compiler) {
    // 指定一個掛載到 webpack 自身的事件鈎子。
    compiler.hooks.emit.tapAsync(
      'MyExampleWebpackPlugin',
      (compilation, callback) => {
        console.log('這是一個示例插件!');
        console.log(
          '這裏表示了資源的單次構建的 `compilation` 對象:',
          compilation
        );

        // 用 webpack 提供的插件 API 處理構建過程
        compilation.addModule(/* ... */);

        callback();
      }
    );
  }
}

一個 plugin 必須包含一個apply函數,它有一個參數compilercompiler是一個包含了完整的 webpack 配置的對象,每次啓動 webpack 構建時它都是唯一存在的。

apply函數中,通過compiler對象監聽 emit 這個 hook 上註冊了一個異步的方法。

可以在 webpack api 中知道,emit 鈎子是一個異步鈎子,因此在示例中用到了tapAsync這個方法往裏加入了插件功能。

emit hook 回調方法中注入了compilation實例,compilation實例能夠訪問當前構建時的所有模塊和相應的依賴。

前面有提到,hook 的類型定義是由 Tapable 提供的,一共有十幾種:

// https://github.com/webpack/tapable/blob/master/lib/index.js
exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesLoopHook = require("./AsyncSeriesLoopHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
exports.HookMap = require("./HookMap");
exports.MultiHook = require("./MultiHook");

webpack 工作流程

webpack 的執行流程涉及兩個關鍵對象:compilercompilation,它們貫穿 webpack 打包構建的
整個生命週期。它們在 webpack 工作流程中在不同的工作節點提供 hook,以便讓開發者註冊插件。

以下是一個 webpack 工作流程的簡圖:

更多 hooks 信息可查閲官網:compiler-hooks、compilation-hooks

自定義 Plugin

現在,我們動手擼一個自定義的 loader。需求是,分析打包輸出的文件大小並生成説明文檔。

首先,我們在項目根目錄下創建文件夾 plugins,並創建一個 plugin 文件 analyze-plugin.js,同時寫入基本內容:

class AnalyzePlugin {
  apply(compiler) {
    ...
  }
}

module.exports = AnalyzePlugin;

我們需要獲取最終輸出的文件內容,因此選擇compileremit hook。這是一個異步鈎子,我們使用 tapAsync方法註冊插件,這種註冊方式需要在回調函數中注入callback方法作為插件執行完成的標識。

compiler.hooks.emit.tapAsync(pluginName, (compilation, callback) => {
  const { assets } = compilation
  let sources = []
  sources.push(`# 文件大小分析`)
  sources.push('| 文件 | 大小(KB) |')
  sources.push('| --- | --- |')
  for (const filePath in assets) {
    sources.push(`| ${filePath} | ${Number((assets[filePath].size() / 1024).toFixed(2))} |`)
  }
  // 添加輸出資源
  const fileContent = sources.join('\n')
  const newAsset = {
    source: () => fileContent,
    size: () => fileContent.length
  };

  // 使用 compilation.emitAsset 方法添加新資源
  compilation.emitAsset('analyze.md', newAsset);
  callback()
});

通過compilation對象,獲取到即將輸出的 assets 內容,對其遍歷並按markdown語法拼接分析文檔的內容。而後,將分析文檔使用添加到打包輸出的文件裏一併生成。

如果希望更靈活一些,比如可以將輸出的文件名、文件標題等信息放在配置中,通過插件的構造函數讀取。

new AnalyzePlugin({
  outputFile: 'analyze.md',
  title: '分析打包資源大小'
})
class AnalyzePlugin {
  constructor(options = {}) {
    // 獲取指定分析文件名與文件標題
    const { outputFile, title } = options
    this.outputFile = outputFile
    this.title = title
  }
  apply(compiler) {
    ...
  }
}

最後,我們執行一下打包命令,查看生成的分析文檔。

文件 大小(KB)
static/51d58c07297bb973f805.jpg 366.95
index.html 7.79
static/css/app.6087d3e9100e1ed1b996.css 0.13
static/js/app.769ebb778600c7d87e2f.js 22.04
static/js/runtime.ef6603f9c6cf8071ccb3.js 7.43

到這,就算是完成了一次自定義 plugin 的開發。如果你有更多的需求,可以繼續嘗試開發。

Github:webpack-template/plugins⭐⭐⭐

更多開發 plugin 的細節可參考官方網站:https://webpack.js.org/contribute/writing-a-plugin/

user avatar grewer Avatar cyzf Avatar alibabawenyujishu Avatar haoqidewukong Avatar zaotalk Avatar smalike Avatar jingdongkeji Avatar qingzhan Avatar kobe_fans_zxc Avatar littlelyon Avatar razyliang Avatar linx Avatar
Favorites 243 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.