Webpack的Plugin看這一篇就夠了

Webpack 插件Plugin是一個 JavaScript 對象醇蝴,它可以通過 Webpack 的插件系統(tǒng)與編譯過程進行交互哩盲。插件通過訂閱特定的鉤子(hooks)來執(zhí)行自定義的邏輯,從而影響構建流程、修改資源瞻鹏、添加額外的功能等寸莫。

每個插件都需要實現(xiàn)一個 apply 方法捺萌,該方法接收一個 compiler 參數(shù),代表當前的編譯器對象膘茎。通過 compiler 對象桃纯,插件可以訪問和操作編譯過程中的各種數(shù)據(jù)和配置酷誓。

開發(fā)一個簡單的 Webpack 插件

讓我們從一個簡單的示例開始,開發(fā)一個 Webpack 插件态坦,它會在構建結束時輸出一條提示信息呛牲。

1. 首先,創(chuàng)建一個名為 CustomPlugin 的文件驮配,并編寫如下代碼:

class CustomPlugin {
  apply(compiler) {
    compiler.hooks.done.tap('CustomPlugin', () => {
      console.log('Build process completed!');
    });
  }
}

module.exports = CustomPlugin;

在這個示例中娘扩,我們創(chuàng)建了一個名為 CustomPlugin 的插件類,并實現(xiàn)了 apply 方法壮锻。在 apply 方法中琐旁,我們通過 compiler.hooks.done.tap 方法訂閱了 done 鉤子,并在構建完成時輸出一條提示信息猜绣。

2. 接下來灰殴,在項目的 Webpack 配置文件中使用該插件。

假設 Webpack 配置文件為 webpack.config.js掰邢,我們可以進行如下配置:

const CustomPlugin = require('./CustomPlugin');

module.exports = {
  // ...其他配置項
  plugins: [
    new CustomPlugin()
  ]
};

通過以上配置牺陶,我們將 CustomPlugin 實例添加到了 Webpack 配置的 plugins 數(shù)組中,使其成為一個生效的插件辣之。

常見應用場景

Webpack 插件的應用場景非常廣泛掰伸,可以根據(jù)具體需求編寫各種功能豐富的插件。以下是一些常見的插件應用場景:

  1. 修改資源:通過訂閱相應的鉤子怀估,插件可以訪問和修改編譯過程中的資源狮鸭,例如替換資源內(nèi)容、添加額外的打包文件等多搀。
class ModifyAssetPlugin {
  apply(compiler) {
    compiler.hooks.emit.tapAsync('ModifyAssetPlugin', (compilation, callback) => {
      // 獲取生成的資源列表
      const assets = compilation.assets;

      // 遍歷資源列表并修改內(nèi)容
      for (const assetName in assets) {
        if (assetName.endsWith('.js')) {
          const asset = assets[assetName];
          const modifiedSource = modifySource(asset.source());
          asset.source = () => modifiedSource;
        }
      }

      callback();
    });
  }
}

在這個示例中歧蕉,ModifyAssetPlugin 插件訂閱了 emit 鉤子,并在構建完成時獲取生成的資源列表康铭。然后惯退,它遍歷資源列表并對 JavaScript 文件進行修改,通過調(diào)用 asset.source() 獲取原始資源內(nèi)容从藤,然后對其進行修改催跪,最后將修改后的內(nèi)容賦值回 asset.source。

  1. 優(yōu)化:插件可以實現(xiàn)各種優(yōu)化策略呛哟,例如代碼壓縮叠荠、文件合并、圖片優(yōu)化等扫责,以提升項目的性能和加載速度榛鼎。
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  // ...其他配置項
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin()
    ]
  }
};

在這個示例中,我們使用了 TerserPlugin 插件來進行代碼的壓縮和混淆。通過將 TerserPlugin 實例添加到 Webpack 配置的 optimization.minimizer 數(shù)組中者娱,可以啟用代碼壓縮功能抡笼。

  1. 注入全局變量:插件可以向打包后的代碼中注入全局變量,以便在應用程序中直接訪問這些變量黄鳍,例如將環(huán)境配置注入到代碼中推姻。
const webpack = require('webpack');

module.exports = {
  // ...其他配置項
  plugins: [
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
    })
  ]
};

在這個示例中,我們使用了 webpack.DefinePlugin 插件來注入全局變量 process.env.NODE_ENV框沟,并將其值設置為 'production'藏古。通過這種方式,可以在打包后的代碼中直接訪問該全局變量忍燥。

  1. 資源管理:通過插件拧晕,可以將構建生成的文件進行復制、移動梅垄、刪除等操作厂捞,以便更好地管理構建產(chǎn)物。
const CopyWebpackPlugin = require('copy-webpack-plugin');

module.exports = {
  // ...其他配置項
  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        { from: 'src/assets', to: 'assets' }
      ]
    })
  ]
};

在這個示例中队丝,我們使用了 CopyWebpackPlugin 插件來復制靜態(tài)資源文件靡馁。通過配置 CopyWebpackPlugin 實例的 patterns 屬性,我們可以指定要復制的文件來源和目標路徑机久。

  1. 自定義模板:插件可以根據(jù)自定義的模板生成特定類型的文件臭墨,例如生成 HTML 文件、生成樣式文件等吞加。
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  // ...其他配置項
  plugins: [
    new HtmlWebpackPlugin({
      template: 'src/index.html',
      filename: 'index.html'
    })
  ]
};

在這個示例中裙犹,我們使用了 HtmlWebpackPlugin 插件來生成 HTML 文件。通過配置 HtmlWebpackPlugin 實例的 template 屬性衔憨,我們可以指定 HTML 模板文件的路徑,然后通過 filename 屬性指定生成的 HTML 文件的名稱袄膏。

這些示例代碼只是展示了常見應用場景的一部分践图,使用的插件也只是其中的一些示例。還有其它應用場景沉馆,像生成Source Map文件码党,多語言國際化,提取CSS文件斥黑,清理輸出目錄等揖盘。

插件的觸發(fā)時機

在 Webpack 的源碼中,當調(diào)用 webpack 命令或使用 Node.js API 運行 Webpack 編譯時锌奴,Webpack 會創(chuàng)建一個 Compiler 實例兽狭。Compiler 是一個編譯器對象,它負責管理整個編譯過程,并在適當?shù)臅r機調(diào)用插件的 apply 方法箕慧。

Webpack 在啟動編譯過程時服球,會創(chuàng)建 Compiler 實例,并通過調(diào)用 Compiler 構造函數(shù)來初始化颠焦。在 Compiler 的構造函數(shù)中斩熊,會調(diào)用 this.hooks 方法來創(chuàng)建各個鉤子(hooks),每個鉤子都是一個 Hook 類型的實例伐庭。

class Compiler {
  constructor() {
    // ...

    this.hooks = Object.freeze({
      /** @type {SyncHook<[]>} */
      initialize: new SyncHook([]),

      /** @type {SyncBailHook<[Compilation], boolean | undefined>} */
      shouldEmit: new SyncBailHook(["compilation"]),
      /** @type {AsyncSeriesHook<[Stats]>} */
      done: new AsyncSeriesHook(["stats"]),
      /** @type {AsyncSeriesHook<[Compiler]>} */
      run: new AsyncSeriesHook(["compiler"]),
      /** @type {AsyncSeriesHook<[Compilation]>} */
      emit: new AsyncSeriesHook(["compilation"])
      // ... 其他鉤子
    })

    // ...
  }
}

Compiler 實例創(chuàng)建后粉渠,Webpack 會遍歷安裝的插件列表,并針對每個插件調(diào)用其 apply 方法圾另。

const createCompiler = rawOptions => {
  //...
    const compiler = new Compiler(options);
  // ...
    if (Array.isArray(options.plugins)) {
        for (const plugin of options.plugins) {
            if (typeof plugin === "function") {
                plugin.call(compiler, compiler);
            } else if (plugin) {
                plugin.apply(compiler);
            }
        }
    }
    // ...
    return compiler;
};

總結起來渣叛,Webpack 在啟動編譯過程時創(chuàng)建了一個 Compiler 實例,并在適當?shù)臅r機調(diào)用每個插件的 apply 方法盯捌,將 Compiler 實例傳遞給插件淳衙。這樣,插件就可以通過訂閱各個鉤子來介入編譯過程并執(zhí)行自定義的邏輯饺著。

插件中的鉤子

  • 每個鉤子都是一個 Hook 類型的實例箫攀,Hook 內(nèi)部完全是基于 tapable 來實現(xiàn)
  • 鉤子的定義通常接收兩個參數(shù):事件名和回調(diào)函數(shù)。事件名用于標識特定的鉤子幼衰,而回調(diào)函數(shù)則是在觸發(fā)該鉤子時執(zhí)行的代碼靴跛。
  • 在 Webpack 中,大多數(shù)鉤子的回調(diào)函數(shù)接收一個參數(shù)渡嚣,通常被命名為 compilation梢睛,它表示當前的編譯實例。compilation 對象包含了與當前編譯相關的信息识椰、資源和依賴關系等绝葡。

compiler 對象提供了各種鉤子(hooks)來讓插件在構建過程的不同階段執(zhí)行自定義邏輯。以下是一些常見的使用場景和對應的鉤子:

  1. 初始化階段(Initialization Phase):

    • entryOption:在解析入口模塊之前調(diào)用腹鹉,可以修改 Webpack 配置的入口選項藏畅。
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.entryOption.tap('MyPlugin', (context, entry) => {
          // 修改入口配置
          // ...
        });
      }
    }
    ```
    
    
  2. 環(huán)境準備階段(Environment Setup Phase):

    • afterEnvironment:在 Webpack 環(huán)境準備好之后調(diào)用,可以擴展環(huán)境或添加全局變量功咒。
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.afterEnvironment.tap('MyPlugin', () => {
          // 擴展環(huán)境或添加全局變量
          // ...
        });
      }
    }
    ```
    
    
  3. 編譯階段(Compilation Phase):

    • make:在創(chuàng)建新的編譯實例之前調(diào)用愉阎,可以創(chuàng)建自定義的編譯實例。
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.make.tap('MyPlugin', (compilation) => {
          // 創(chuàng)建自定義的編譯實例
          // ...
        });
      }
    }
    ```
    
    
  4. 編譯過程階段(Compilation Process Phase):

    • compilation:在每次創(chuàng)建新的編譯實例時調(diào)用力奋,可以訪問和修改編譯過程中的資源和依賴關系榜旦。
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
          // 訪問和修改編譯過程中的資源和依賴關系
          // ...
        });
      }
    }
    ```
    
    
  5. 構建完成階段(Build Completion Phase):

    • done:在構建完成后調(diào)用,可以獲取和處理構建結果的統(tǒng)計信息景殷。
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.done.tap('MyPlugin', (stats) => {
          // 獲取和處理構建結果的統(tǒng)計信息
          // ...
        });
      }
    }
    ```
    
    
  6. 解析階段(Resolve Phase):

    • beforeResolve:在解析模塊路徑之前調(diào)用溅呢,可以修改模塊的解析規(guī)則或路徑澡屡。
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.beforeResolve.tap('MyPlugin', (resolveData) => {
          // 修改模塊的解析規(guī)則或路徑
          // ...
        });
      }
    }
    ```
    
    
  7. 優(yōu)化階段(Optimization Phase):

    • optimize:在優(yōu)化階段開始之前調(diào)用,可以自定義優(yōu)化邏輯藕届。
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.optimize.tap('MyPlugin', (compilation) => {
          // 自定義優(yōu)化邏輯
          // ...
        });
      }
    }
    ```
    
    
  8. 生成資源階段(Asset Generation Phase):

    • emit:在生成最終資源之前調(diào)用挪蹭,可以訪問和修改最終生成的資源。
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.emit.tap('MyPlugin', (compilation) => {
          // 訪問和修改最終生成的資源
          // ...
        });
      }
    }
    ```
    
    
  9. 清理階段(Cleanup Phase):

    • afterEmit:在生成最終資源之后調(diào)用休偶,可以執(zhí)行一些清理操作或觸發(fā)其他任務梁厉。
    class MyPlugin {
      apply(compiler) {
        compiler.hooks.afterEmit.tap('MyPlugin', (compilation) => {
          // 執(zhí)行清理操作或觸發(fā)其他任務
          // ...
        });
      }
    }
    ```
    
    

上面演示了一些場景下鉤子對構建過程的不同階段更精細的控制和處理能力的支持。根據(jù)具體的需求和業(yè)務邏輯踏兜,可以選擇適合的鉤子并在插件中編寫相應的邏輯代碼词顾。

通過開發(fā)自己的插件,可以根據(jù)項目需求添加自定義邏輯碱妆、優(yōu)化構建過程肉盹,并實現(xiàn)更高效的前端開發(fā)流程。希望讀到本文的都有所收獲疹尾,共勉~

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末上忍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子纳本,更是在濱河造成了極大的恐慌窍蓝,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件繁成,死亡現(xiàn)場離奇詭異吓笙,居然都是意外死亡,警方通過查閱死者的電腦和手機巾腕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門面睛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人尊搬,你說我怎么就攤上這事叁鉴。” “怎么了毁嗦?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵亲茅,是天一觀的道長。 經(jīng)常有香客問我狗准,道長,這世上最難降的妖魔是什么茵肃? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任腔长,我火速辦了婚禮,結果婚禮上验残,老公的妹妹穿的比我還像新娘捞附。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布鸟召。 她就那樣靜靜地躺著胆绊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪欧募。 梳的紋絲不亂的頭發(fā)上压状,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天,我揣著相機與錄音跟继,去河邊找鬼种冬。 笑死,一個胖子當著我的面吹牛舔糖,可吹牛的內(nèi)容都是我干的娱两。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼金吗,長吁一口氣:“原來是場噩夢啊……” “哼十兢!你這毒婦竟也來了?” 一聲冷哼從身側響起摇庙,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤旱物,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后跟匆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體异袄,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年玛臂,在試婚紗的時候發(fā)現(xiàn)自己被綠了烤蜕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡迹冤,死狀恐怖讽营,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情泡徙,我是刑警寧澤橱鹏,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站堪藐,受9級特大地震影響莉兰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜礁竞,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一糖荒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧模捂,春花似錦捶朵、人聲如沸蜘矢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽品腹。三九已至,卻和暖如春红碑,著一層夾襖步出監(jiān)牢的瞬間舞吭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工句喷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留镣典,地道東北人。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓唾琼,卻偏偏與公主長得像兄春,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容