webpack 提供了一個如何開發 webpack 插件的介紹,你可以直接訪問這里查看,這里提供一個擴展 HtmlWebpackPlugin 的開發實例。
前面我們介紹過 HtmlWebpackPlugin, 這個插件允許將 webpack 動態打包的輸出注入到頁面中,但是,有的時候我們需要在這個頁面中注入一些自定義的樣式表或者腳本,HtmlWebpackPlugin 並不支持這個特性。有人向插件作者提了建議,這里是討論的內容,結果是插件提供了幾個事件來支持自己來實現這個特性。我們通過一個實例來演示如何使用這些事件來擴展 webpack。
需求
我們希望能夠自動插入一個腳本的 script 在 webpack 生成的 script 之前,以便提前加載我們自定義的數據。最后生成的 HTML 類似這樣的效果。
<script type="text/javascript" src="./configuration/config.js"></script> <script type="text/javascript" src="style.bundle.js"></script> <script type="text/javascript" src="app.bundle.js"></script>
第一行是我們期望注入的腳本,其它兩行是 webpack 導出的腳本。
插件入門
作為一個 webpack 的插件,使用方式是這樣的。
plugins: [ new MyPlugin({ paths: ["./configuration/config.js"] }), new HtmlwebpackPlugin({ title: 'Hello Angular2!', template: './src/index.html', inject: true }) ],
所有的插件定義在 plugins 中,插件組成的一個數組,每個元素是一個插件的對象實例,具體傳遞什么參數,是你自己定義的。
從使用方式中可以看出,其實我們需要一個 JavsScript 的類函數,也就是說,寫 webpack 插件就是定義一個這樣的函數,這個函數需要接收參數。
webpack 還要求這個對象提供一個名為 apply 的函數,這個函數定義在插件的原型上,webpack 會調用插件實例的這個方法,在調用的時候還會傳遞一個參數,以便我們訪問 webpack 的上下文信息。
官方提供的實例函數如下,最后一行是使用 CommonJs 風格導出這個插件。
function HelloWorldPlugin(options) { // Setup the plugin instance with options... } HelloWorldPlugin.prototype.apply = function(compiler) { compiler.plugin('done', function() { console.log('Hello World!'); }); }; module.exports = HelloWorldPlugin;
傳遞參數
在我們的需求中,我們希望傳遞一個名為 paths 的路徑參數,其中的每個路徑需要生成一個 script 元素,插入到 webpack 導出的 script 之前。
new MyPlugin({ paths: ["./configuration/config.js"] }),
在我們的插件中,需要保存這個參數,以便在 apply 函數中使用。
function MyPlugin(options) { // Configure your plugin with options... this.options = options; }
直接保存到當前的對象實例中,在配合 new 的時候,this 就是剛剛創建的插件對象實例了。
實現
在 webpack 調用插件對象的 apply 方式的時候,我們首先應該獲取我們保存的參數,使用 this 訪問當前對象,獲取剛剛保存的參數。
MyPlugin.prototype.apply = function(compiler) { // ... var paths = this.options.paths; };
在我們的 apply 方法內,需要調用 compiler 的 plugin 函數。這個函數注冊到 webpack 各個處理階段上,可以支持的參數有:
我們這里使用了 compilation 編譯任務。
MyPlugin.prototype.apply = function(compiler) { var paths = this.options.paths; compiler.plugin('compilation', function(compilation, options) {
}); };
webpack 會給我們提供的回調函數提供參數,我們可以注冊編譯階段的事件了。html-webpack-plugin 提供了一系列事件。
Async:
html-webpack-plugin-before-html-generation
html-webpack-plugin-before-html-processing
html-webpack-plugin-alter-asset-tags
html-webpack-plugin-after-html-processing
html-webpack-plugin-after-emit
Sync:
html-webpack-plugin-alter-chunks
我們可以注冊到它處理 HTML 之前,使用 html-webpack-plugin-before-html-processing 事件。
MyPlugin.prototype.apply = function(compiler) { var paths = this.options.paths; compiler.plugin('compilation', function(compilation, options) { compilation.plugin('html-webpack-plugin-before-html-processing', function(htmlPluginData, callback) {
......
}); }); };
在這個回調函數中,我們可以得到 html-webpack-plugin 提供的上下文對象,比如,它准備生成 script 所對應的 javascript 文件路徑就保存在 htmlPluginData.assets.js 數組中,它會根據這個數組中的路徑,依次生成 script 元素,然后插入到 Html 網頁中。
我們需要的就是就我們的路徑插入到這個數組的前面。
MyPlugin.prototype.apply = function(compiler) { var paths = this.options.paths; compiler.plugin('compilation', function(compilation, options) { compilation.plugin('html-webpack-plugin-before-html-processing', function(htmlPluginData, callback) { for (var i = paths.length - 1; i >= 0; i--) { htmlPluginData.assets.js.unshift(paths[i]); } callback(null, htmlPluginData); }); }); };
完整的插件代碼如下所示。
function MyPlugin(options) { this.options = options; } MyPlugin.prototype.apply = function(compiler) { var paths = this.options.paths; compiler.plugin('compilation', function(compilation, options) { compilation.plugin('html-webpack-plugin-before-html-processing', function(htmlPluginData, callback) { for (var i = paths.length - 1; i >= 0; i--) { htmlPluginData.assets.js.unshift(paths[i]); } callback(null, htmlPluginData); }); }); }; module.exports = MyPlugin;
最后一行是導出我們的插件。
討論
通過 webpack 的插件機制,我們可以自由地擴展 webpack ,實現我們需要的特性。
See Also: