【Webpack】Webpack 插件開發如此簡單!
本文使用的Webpack-Quickly-Starter快速搭建 Webpack4 本地學習環境。 建議多閱讀 Webpack 文檔《Writing a Plugin》章節,學習開發簡單插件。
”
本文將帶你一起開發你的第一個 Webpack 插件,從 Webpack 配置工程師,邁向 Webpack 開發工程師!做自己的輪子,讓別人用去吧。
完整代碼存放在:https://github.com/pingan8787/script-timestamp-webpack-plugin
一、背景介紹
本文靈感源自業務中的經驗總結,不怕神一樣的產品,只怕一根筋的開發。
在項目打包遇到問題:“當項目託管到 CDN 平臺,希望實現項目中的 index.js 不被緩存”。因為我們需要修改 index.js 中的內容,不想用戶被緩存。
思考一陣,有這麼幾種思路:
- 在 CDN 平臺中過濾該文件的緩存設置;
- 查找 DOM 元素,修改該>
- 打包時動態創建>
(聰明的你還有其他方法,歡迎討論)
思路分析:
- 顯然修改 CDN 設置的話,治標不治本;
- 在模版文件中,添加>
- 需要在 index.html 生成之前,修改 js 文件的路徑,並添加時間戳。
於是我準備使用第三種方式,在 index.html 生成之前完成下面修改:
問題簡單,實際還是想試試開發 Webpack Plugin。
二、基礎知識
Webpack 使用階段式的構建回調,開發者可以引入它們自己的行為到 Webpack 構建流程中。在開發之前,需要了解以下 Webpack 相關概念:
2.1 Webpack 插件組成
在自定義插件之前,我們需要了解,一個 Webpack 插件由哪些構成,下面摘抄文檔:
- 一個具名 JavaScript 函數;
- 在它的原型上定義 apply 方法;
- 指定一個觸及到 Webpack 本身的事件鉤子;
- 操作 Webpack 內部的實例特定數據;
- 在實現功能後調用 Webpack 提供的 callback。
2.2 Webpack 插件基本架構
插件由一個構造函數實例化出來。構造函數定義 apply 方法,在安裝插件時,apply 方法會被 Webpack compiler 調用一次。apply 方法可以接收一個 Webpack compiler對象的引用,從而可以在回調函數中訪問到 compiler 對象。
官方文檔提供一個簡單的插件結構:
<code>class HelloWorldPlugin {
apply(compiler) {
compiler.hooks.done.tap('Hello World Plugin', (
stats /* 在 hook 被觸及時,會將 stats 作為參數傳入。 */
) => {
console.log('Hello World!');
});
}
}
module.exports = HelloWorldPlugin;
/<code>
使用插件:
<code>// webpack.config.js
var HelloWorldPlugin = require('hello-world');
module.exports = {
// ... 這裡是其他配置 ...
plugins: [new HelloWorldPlugin({ options: true })]
};
/<code>
2.3 HtmlWebpackPlugin 介紹
HtmlWebpackPlugin 簡化了 HTML 文件的創建,以便為你的 Webpack 包提供服務。這對於在文件名中包含每次會隨著編譯而發生變化哈希的 webpack bundle 尤其有用。
”
插件的基本作用概括:生成 HTML 文件。
html-webapck-plugin 插件
兩個主要作用:- 為 HTML 文件引入外部資源(如>
- 動態創建 HTML 入口文件,如單頁應用的 index.html文件。
html-webapck-plugin 插件原理介紹:
- 讀取 Webpack 中 entry 配置的相關入口 chunk 和 extract-text-webpack-plugin 插件抽取的 CSS 樣式;
- 將樣式插入到插件提供的 template 或 templateContent 配置指定的模版文件中;
- 插入方式是:通過 link 標籤引入樣式,通過>
三、開發流程
本文開發的 自動添加時間戳引用腳本文件(SetScriptTimestampPlugin) 插件實現的原理:通過 HtmlWebpackPlugin 生成 HTML 文件前,將模版文件預留位置替換成腳本,腳本中執行自動添加時間戳來引用腳本文件。
3.1 插件運行機制
SetScriptTimestampPlugin 運行機制.png
3.2 初始化插件文件
新建 SetScriptTimestampPlugin.js 文件,並參考官方文檔中插件的基本結構,初始化插件代碼:
<code>// SetScriptTimestampPlugin.js
class SetScriptTimestampPlugin {
apply(compiler) {
compiler.hooks.done.tap('SetScriptTimestampPlugin',
(compilation, callback) => {
console.log('SetScriptTimestampPlugin!');
});
}
}
module.exports = SetScriptTimestampPlugin;
/<code>
apply 方法為插件原型方法,接收 compiler 作為參數。
3.3 選擇插件觸發時機
選擇插件觸發時機,其實是選擇插件觸發的 compiler 鉤子(即何時觸發插件)。Webpack 提供鉤子有很多,這裡簡單介紹幾個,完整具體可參考文檔《Compiler Hooks》:
- entryOption : 在 webpack 選項中的 entry 配置項 處理過之後,執行插件。
- afterPlugins : 設置完初始插件之後,執行插件。
- compilation : 編譯創建之後,生成文件之前,執行插件。。
- emit : 生成資源到 output 目錄之前。
- done : 編譯完成。
我們插件應該是要在 HTML 輸出之前,動態添加>
<code>// SetScriptTimestampPlugin.js
class SetScriptTimestampPlugin {
apply(compiler) {
- compiler.hooks.done.tap('SetScriptTimestampPlugin',
+ compiler.hooks.compilation.tap('SetScriptTimestampPlugin',
(compilation, callback) => {
console.log('SetScriptTimestampPlugin!');
});
}
}
module.exports = SetScriptTimestampPlugin;
/<code>
在 compiler.hooks 下指定事件鉤子函數,便會觸發鉤子時,執行回調函數。Webpack 提供三種觸發鉤子的方法:
- tap :以同步方式觸發鉤子;
- tapAsync :以異步方式觸發鉤子;
- tapPromise :以異步方式觸發鉤子,返回 Promise;
這三種方式能選擇的鉤子方法也不同,由於 compilation 是 SyncHook 同步鉤子,所以採用 tap 觸發方式。tap 方法接收兩個參數:插件名稱和回調函數。
3.4 添加插件替換入口
我們原理上是將模版文件中,指定替換入口,再替換成需要執行的腳本。
image.png
所以我們在模版文件 template.html 中添加
閱讀更多 前端自習課 的文章
關鍵字: JavaScript HTML 思考