在Webpack學習-工做原理(上)一文中咱們就已經介紹了Plugin
的基本概念,同時知道了webpack其實很像一條生產線,要通過一系列處理流程後才能將源文件轉換成咱們理想的輸出結果。而webpack構建過程當中,會在特定的時機廣播對應的事件,插件能夠監聽這些事件的發生,Plugin
在webpack構建流程中就是這樣的一個角色。同時咱們也介紹了不少整個構建流程會廣播的事件,那麼這篇文章咱們一塊兒詳細地學習一下如何編寫Plugin
。javascript
其實Plugin
本質上就是一個class,一個最基礎的Plugin
代碼以下:css
class BasePlugin { // 構造函數,接收options配置 constructor(options) { ... } apply(compiler) { // 在此處去監聽webpack廣播的全部事件 compiler.plugin('compilation', function(compilation) { ... }); } } moudle.exports = BasePlugin;
咱們能夠再看看,webpack會怎麼配置Plugin
,java
module.exports = { plugins: [ new BasePlugin(options) ] }
咱們回憶一下,Webpack學習-工做原理(上)文章中們介紹過webpack的構建詳細流程,初始化的時候會去new Plugin()
,那麼即是會去實例化webpack配置plugins全部的插件,那麼第一步插件實例化就有了,而插件中的apply方法會在開始編譯時依次被調用,而且傳入Compiler對象(後面會深刻介紹),而後調用Compiler.run()開始編譯。webpack
- Compiler對象包含了Webpack環境全部的配置信息,包含options,loaders,plugins這些信息,這個對象在webpack啓動時被實例化,全局惟一,能夠簡單理解成就是webpack實例
- Compilation表明着一次新的編譯,包含當前的模塊資源、編譯生成的資源,變化的文件,以前咱們瞭解到compilation事件中compilation對象也會提供不少事件給插件作擴展,同時不少事件的的回調中都會將compilation傳入,以便使用
- Webpack的事件機制應用了觀察者模式,Compiler和Compilation同時繼承Taptable,因此能夠直接在Compiler和Compilation對象廣播和監聽事件,廣播事件
[Compiler | Compilation].apply('event-name', params)
,監聽事件[Compiler | Compilation].plugin('event-name', function(params){...})
,event-name不能和現有的事件重名
- 只要能拿到Compiler或是Compilation對象,就能廣播新的事件,供其餘插件使用
- Compiler或是Compilation對象爲同一個引用,一旦修改就會影響後面的插件
- 若是事件是異步的,會帶兩個參數,第二個參數爲回調函數,在插件處理完任務時須要調用回調函數通知webpack,纔會進入下一個處理流程。如:
compiler.plugin('emit',function(compilation, callback) { // 支持處理邏輯 // 處理完畢後執行 callback 以通知 Webpack // 若是不執行 callback,運行流程將會一直卡在這不往下執行 callback(); });
class Plugin { apply(compiler) { compiler.plugin('emit', function (compilation, callback) { // compilation.chunks 存放全部代碼塊,是一個數組 compilation.chunks.forEach(function (chunk) { // chunk 表明一個代碼塊 // 代碼塊由多個模塊組成,經過 chunk.forEachModule 能讀取組成代碼塊的每一個模塊 chunk.forEachModule(function (module) { // module 表明一個模塊 // module.fileDependencies 存放當前模塊的全部依賴的文件路徑,是一個數組 module.fileDependencies.forEach(function (filepath) { }); }); // Webpack 會根據 Chunk 去生成輸出的文件資源,每一個 Chunk 都對應一個及其以上的輸出文件 // 例如在 Chunk 中包含了 CSS 模塊而且使用了 ExtractTextPlugin 時, // 該 Chunk 就會生成 .js 和 .css 兩個文件 chunk.files.forEach(function (filename) { // compilation.assets 存放當前全部即將輸出的資源 // 調用一個輸出資源的 source() 方法能獲取到輸出資源的內容 let source = compilation.assets[filename].source(); }); }); // 這是一個異步事件,要記得調用 callback 通知 Webpack 本次事件監聽處理結束。 // 若是忘記了調用 callback,Webpack 將一直卡在這裏而不會日後執行。 callback(); }) } }
// 當依賴的文件發生變化時會觸發 watch-run 事件 compiler.plugin('watch-run', (watching, callback) => { // 獲取發生變化的文件列表 const changedFiles = watching.compiler.watchFileSystem.watcher.mtimes; // changedFiles 格式爲鍵值對,鍵爲發生變化的文件路徑。 if (changedFiles[filePath] !== undefined) { // filePath 對應的文件發生了變化 } callback(); });
compiler.plugin('after-compile', (compilation, callback) => { // 把 HTML 文件添加到文件依賴列表,好讓 Webpack 去監聽 HTML 模塊文件,在 HTML 模版文件發生變化時從新啓動一次編譯 compilation.fileDependencies.push(filePath); callback(); });
// 設置 compilation.assets 的代碼以下: compiler.plugin('emit', (compilation, callback) => { // 設置名稱爲 fileName 的輸出資源 compilation.assets[fileName] = { // 返回文件內容 source: () => { // fileContent 既能夠是表明文本文件的字符串,也能夠是表明二進制文件的 Buffer return fileContent; }, // 返回文件大小 size: () => { return Buffer.byteLength(fileContent, 'utf8'); } }; callback(); }); // 讀取 compilation.assets 的代碼以下: compiler.plugin('emit', (compilation, callback) => { // 讀取名稱爲 fileName 的輸出資源 const asset = compilation.assets[fileName]; // 獲取輸出資源的內容 asset.source(); // 獲取輸出資源的文件大小 asset.size(); callback(); });
// 判斷當前配置使用使用了 ExtractTextPlugin, // compiler 參數即爲 Webpack 在 apply(compiler) 中傳入的參數 function hasExtractTextPlugin(compiler) { // 當前配置全部使用的插件列表 const plugins = compiler.options.plugins; // 去 plugins 中尋找有沒有 ExtractTextPlugin 的實例 return plugins.find(plugin=>plugin.__proto__.constructor === ExtractTextPlugin) != null; }
extract-text-webpack-plugin
插件進行斷點調試的截圖,能夠來看看這兩個分別打印出來的東西,通常狀況下,咱們是不須要去寫Plugin
,可是有時候咱們有些業務需求是沒有插件能夠知足的,那麼咱們便得須要本身去寫Plugin
,那瞭解Plugin
的一些相關知識點就是有必要的,咱們不必定要每一個鉤子或是API都至關熟,可是咱們須要思路,瞭解如何編寫Plugin
,也是有必要的,Plugin
中最重要的compiler和compilation,一個Plugin
插件也就是圍繞着這個去擴展,對應詳細內容能夠去webpack官網瞭解,compiler連接,compilation連接。web