上次寫了 如何編寫一個 Webpack Loader,今天來說說如何編寫一個 Webpack Plugin。
webpack 內部執行流程一次完整的 webpack 打包大致是這樣的過程:
將命令行參數與 webpack 配置文件 合併、解析得到參數對象。參數對象傳給 webpack 執行得到 Compiler 對象。執行 Compiler 的 run 方法開始編譯。每次執行 run 編譯都會生成一個 Compilation 對象。觸發 Compiler 的 make 方法分析入口文件,調用 compilation 的 buildModule 方法創建主模塊對象。生成入口文件 AST(抽象語法樹),通過 AST 分析和遞歸加載依賴模塊。所有模塊分析完成後,執行 compilation 的 seal 方法對每個 chunk 進行整理、優化、封裝。最後執行 Compiler 的 emitAssets 方法把生成的文件輸出到 output 的目錄中。Plugin 作用按我的理解,Webpack 插件的作用就是在 webpack 運行到某個時刻的時候,幫我們做一些事情。
在 Webpack 運行的生命周期中會廣播出許多事件,Plugin 可以監聽這些事件,在合適的時機通過 Webpack 提供的 API 改變輸出結果。
官方解釋是:
❝插件向第三方開發者提供了 webpack 引擎中完整的能力。使用階段式的構建回調,開發者可以引入它們自己的行為到 webpack 構建流程中。
❞編寫 Pluginwebpack 插件的組成:
一個 JS 命名函數或一個類(可以想下我們平時使用插件就是 new XXXPlugin()的方式)在插件類/函數的 (prototype) 上定義一個 apply 方法。通過 apply 函數中傳入 compiler 並插入指定的事件鉤子,在鉤子回調中取到 compilation 對象通過 compilation 處理 webpack 內部特定的實例數據如果是插件是異步的,在插件的邏輯編寫完後調用 webpack 提供的 callback比如我們寫一個插件,生成一個版權的文件。
基本雛形function CopyrightWebpackPlugin() {}
CopyrightWebpackPlugin.prototype.apply = function (compiler) {}
module.exports = CopyrightWebpackPlugin也可以寫成類的形式:
class CopyrightWebpackPlugin {
apply(compiler) {
console.log(compiler)
}
}
module.exports = CopyrightWebpackPluginwebpack 在啟動之後,在讀取配置的過程中會先執行new CopyrightWebpackPlugin(options)操作,初始化一個CopyrightWebpackPlugin實例對象。在初始化 compiler 對象之後,會調用上述實例對象的apply方法並將compiler對象傳入。
在apply方法中,通過compiler對象來監聽 webpack 生命周期中廣播出來的事件,我們也可以通過 compiler 對象來操作 webpack 的輸出。
Compiler 和 Compilation在插件開發中最重要的兩個對象是 compiler 和 compilation 對象。
❝compiler 對象代表了完整的 webpack 環境配置,在初始化 compiler 對象之後,通過調用插件實例的 apply 方法,作為其參數傳入。這個對象在啟動 webpack 時被一次性建立,並包含了 webpack 環境的所有的配置信息,包括 options,loader 和 plugin。當在 webpack 環境中應用一個插件時,插件將收到此 compiler 對象的引用。可以使用它來訪問 webpack 的主環境。
❞❝compilation 對象會作為 plugin 內置事件回調函數的參數,一個 compilation 對象包含了當前的模塊資源、編譯生成資源、變化的文件以及被跟蹤依賴的狀態信息。當 Webpack 以開發模式運行時,每當檢測到一個文件變化,一次新的 compilation 將被創建。compilation 對象也提供了很多事件回調供插件做擴展。通過 compilation 也能讀取到 compiler 對象。
❞編碼下面代碼為生成一個版權 txt 文件,新建文件src/plugins/copyright-webpack-plugin.js:
class CopyrightWebpackPlugin {
apply(compiler) {
// emit 鉤子是生成資源到 output 目錄之前執行,emit 是一個異步串行鉤子,需要用 tapAsync 來註冊
compiler.hooks.emit.tapAsync('CopyrightWebpackPlugin', (compilation, callback) => {
// 回調方式註冊異步鉤子
const copyrightText = '版權歸 JackySummer 所有'
// compilation存放了這次打包的所有內容
// 所有待生成的文件都在它的 assets 屬性上
compilation.assets['copyright.txt'] = {
// 添加copyright.txt
source: function () {
return copyrightText
},
size: function () {
// 文件大小
return copyrightText.length
},
}
callback() // 必須調用
})
}
}
module.exports = CopyrightWebpackPluginwebpack 中許多對象擴展自 Tapable 類。這個類暴露 tap, tapAsync 和 tapPromise 方法,可以使用這些方法,注入自定義的構建步驟,這些步驟將在整個編譯過程中不同時機觸發。
使用 tapAsync 方法來訪問插件時,需要調用作為最後一個參數提供的回調函數。
在 webpack.config.js
const path = require('path')
const CopyrightWebpackPlugin = require('./src/plugins/copyright-webpack-plugin')
module.exports = {
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
},
plugins: [new CopyrightWebpackPlugin()],
}執行 webpack 命令,就會看到 dist 目錄下生成copyright.txt文件
如果在配置文件使用 plugin 時傳入參數該怎麼獲得呢,可以在插件類添加構造函數拿到:
plugins: [
new CopyrightWebpackPlugin({
name: 'jacky',
}),
],在copyright-webpack-plugin.js中
class CopyrightWebpackPlugin {
constructor(options = {}) {
console.log('options', options) // options { name: 'jacky' }
}
}參考文章:揭秘 webpack plugin
❝歡迎關注我掘金帳號和Github技術博客:
掘金:https://juejin.im/user/1257497033714477Github:https://github.com/Jacky-Summer覺得對你有幫助或有啟發的話歡迎 star,你的鼓勵是我持續創作的動力~如需在微信公眾號平臺轉載請聯繫作者授權同意,其它途徑轉載請在文章開頭註明作者和文章出處。❞