插件將Webpack引擎的全部潛能暴露給第三方開發人員。使用分段構建回調,開發人員可以將自己的行爲引入Webpack構建過程。插件的開發比起加載器(loaders)的開發更近了一步,因爲你需要用一些webpack底層的內部構件來鉤住他們。準備去閱讀一下webpack的源碼吧。
編譯器(compiler)和編譯(compilation)
在開發插件時,兩個最重要的對象是:compiler和compilation。瞭解他們的角色是擴展Webpack引擎的重要的第一步。
- compiler對象表示完全配置的webpack環境,這個對象在webpack啓動時,就會創建,並配置了所有操作設置包括options,loaders,plugins等。當一個插件應用於Webpack環境時,插件將接收對此compiler的一個引用, 使用compiler去訪問Webpack主環境。
- 一個compilation對象表示一個單一構建的版本花資產(asset)。在運行Webpack開發中間件時,每次檢測到文件更改時都將創建一個新的編譯,從而生成一組新的已編譯資產。一個compilation表達有compilation還提供了許多回調點,插件可以選擇執行自定義操作。
這兩個組件是任何Webpack插件(特別是一個編譯)的一個組成部分,因此開發人員要熟悉這些源文件:
基本結構
插件是在它們的原型上具有apply方法的可實例化對象。這個apply方法在安裝插件時由Webpack編譯器調用一次。 apply方法被引用到底層的Webpack compiler,它允許訪問compiler回調。 一個簡單的插件的結構如下:
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;
然後安裝插件,只需在你的Webpack config plugins數組中包含一個實例:
var HelloWorldPlugin = require('hello-world');
var webpackConfig = {
// ... config settings here ...
plugins: [
new HelloWorldPlugin({options: true})
]
};
訪問編譯對象
使用編譯器對象,您可以綁定回調,爲每個新編譯提供引用。 這些編譯提供了用於在構建過程中掛鉤到許多步驟的回調。
function HelloCompilationPlugin(options) {}
HelloCompilationPlugin.prototype.apply = function(compiler) {
// Setup callback for accessing a compilation:
compiler.plugin("compilation", function(compilation) {
// Now setup callbacks for accessing compilation steps:
compilation.plugin("optimize", function() {
console.log("Assets are being optimized.");
});
});
};
module.exports = HelloCompilationPlugin;
有關編譯器,編譯和其他重要對象可用的回調的更多信息,請參閱插件API文檔。
異步編譯插件
一些編譯插件步驟是異步的,並且傳遞一個回調函數,當插件完成運行時必須調用它。
function HelloAsyncPlugin(options) {}
HelloAsyncPlugin.prototype.apply = function(compiler) {
compiler.plugin("emit", function(compilation, callback) {
// Do something async...
setTimeout(function() {
console.log("Done with async work...");
callback();
}, 1000);
});
};
module.exports = HelloAsyncPlugin;
一個簡單的示例
一旦我們可以鎖定到Webpack編譯器和每個單獨的編譯,,我們可以用webpack引擎做我們想做的事情。我們可以重新格式化現有文件,創建衍生文件或製作全新的資產。
讓我們編寫一個簡單的示例插件,生成一個名爲filelist.md的新構建文件; 其內容將列出我們構建中的所有資產文件。 這個插件可能看起來像這樣:
function FileListPlugin(options) {}
FileListPlugin.prototype.apply = function(compiler) {
compiler.plugin('emit', function(compilation, callback) {
// Create a header string for the generated file:
var filelist = 'In this build:\n\n';
// Loop through all compiled assets,
// adding a new line item for each filename.
for (var filename in compilation.assets) {
filelist += ('- '+ filename +'\n');
}
// Insert this list into the Webpack build as a new file asset:
compilation.assets['filelist.md'] = {
source: function() {
return filelist;
},
size: function() {
return filelist.length;
}
};
callback();
});
};
module.exports = FileListPlugin;
有用的插件模式
插件給予了無限的機會在Webpack構建系統中執行自定義。這允許您創建自定義資產類型,執行唯一的構建修改,甚至在使用中間件時增強Webpack運行時。以下是在編寫插件時非常有用的Webpack的一些功能。
瀏覽資產,塊,模塊和依賴關係
在編譯被密封之後,編譯中的所有結構可以被遍歷。
function MyPlugin() {}
MyPlugin.prototype.apply = function(compiler) {
compiler.plugin('emit', function(compilation, callback) {
// Explore each chunk (build output):
compilation.chunks.forEach(function(chunk) {
// Explore each module within the chunk (built inputs):
chunk.modules.forEach(function(module) {
// Explore each source file path that was included into the module:
module.fileDependencies.forEach(function(filepath) {
// we've learned a lot about the source structure now...
});
});
// Explore each asset filename generated by the chunk:
chunk.files.forEach(function(filename) {
// Get the asset source for each file generated by the chunk:
var source = compilation.assets[filename].source();
});
});
callback();
});
};
module.exports = MyPlugin;
- compilation.modules:一個編譯中的模塊數組(構建輸入)。 每個模塊管理一個源庫中的原文件的構建。
- module.fileDependencies:包含在模塊中的源文件路徑數組。這包括源JavaScript文件本身(例如:index.js)和所需的所有依賴關係資產文件(樣式表,圖像等)。 查看依賴關係對於查看哪些源文件屬於模塊很有用。
- compilation.chunks: 編譯中的塊數組(構建輸出)。 每個塊管理最終渲染資產的組成。
- chunk.modules: 包含在塊中的模塊數組。通過擴展,您可以查看每個模塊的依賴關係來查看饋入到一個塊中的原始源文件。
- chunk.files:由塊生成的輸出文件名的數組。 您可以從compilation.assets表中訪問這些資產來源。
監控文件監視圖
在運行Webpack中間件時,每個編譯包括一個fileDependencies數組(什麼文件被監視)和一個fileTimestamps哈希,將觀察到的文件路徑映射到時間戳。這些對於檢測編譯期間已更改的文件非常有用:
function MyPlugin() {
this.startTime = Date.now();
this.prevTimestamps = {};
}
MyPlugin.prototype.apply = function(compiler) {
compiler.plugin('emit', function(compilation, callback) {
var changedFiles = Object.keys(compilation.fileTimestamps).filter(function(watchfile) {
return (this.prevTimestamps[watchfile] || this.startTime) < (compilation.fileTimestamps[watchfile] || Infinity);
}.bind(this));
this.prevTimestamps = compilation.fileTimestamps;
callback();
}.bind(this));
};
module.exports = MyPlugin;
您還可以將新的文件路徑饋送到監視圖中,以在這些文件更改時接收編譯觸發器。 只需將有效的文件路徑推送到compilation.fileDependencies數組中,即可將它們添加到watch中。 注意:fileDependencies數組在每個編譯中重建,因此您的插件必須將自己的監視依賴項推送到每個編譯中,以保持它們被觀察。
更改塊
與監視圖類似,通過跟蹤它們的哈希來監視編譯中的改變塊(或模塊)是相當簡單的。
function MyPlugin() {
this.chunkVersions = {};
}
MyPlugin.prototype.apply = function(compiler) {
compiler.plugin('emit', function(compilation, callback) {
var changedChunks = compilation.chunks.filter(function(chunk) {
var oldVersion = this.chunkVersions[chunk.name];
this.chunkVersions[chunk.name] = chunk.hash;
return chunk.hash !== oldVersion;
}.bind(this));
callback();
}.bind(this));
};
module.exports = MyPlugin;