原文地址:http://wushaobin.top/2019/03/15/webpackPlugin/ 什麼是Plugin? 在 "Webpack學習-工作原理(上)" 一文中我們就已經介紹了 的基本概念,同時知道了webpack其實很像一條生產線,要經過一系列處理流程後才能將源文件轉換成我們理想的 ...
什麼是Plugin?
在Webpack學習-工作原理(上)一文中我們就已經介紹了Plugin
的基本概念,同時知道了webpack其實很像一條生產線,要經過一系列處理流程後才能將源文件轉換成我們理想的輸出結果。而webpack構建過程中,會在特定的時機廣播對應的事件,插件可以監聽這些事件的發生,Plugin
在webpack構建流程中就是這樣的一個角色。同時我們也介紹了很多整個構建流程會廣播的事件,那麼這篇文章我們一起詳細地學習一下如何編寫Plugin
。
其實Plugin
本質上就是一個class,一個最基礎的Plugin
代碼如下:
class BasePlugin {
// 構造函數,接收options配置
constructor(options) {
...
}
apply(compiler) {
// 在此處去監聽webpack廣播的所有事件
compiler.plugin('compilation', function(compilation) {
...
});
}
}
moudle.exports = BasePlugin;
我們可以再看看,webpack會怎麼配置Plugin
,
module.exports = {
plugins: [
new BasePlugin(options)
]
}
我們回憶一下,Webpack學習-工作原理(上)文章中們介紹過webpack的構建詳細流程,初始化的時候會去new Plugin()
,那麼便是會去實例化webpack配置plugins所有的插件,那麼第一步插件實例化就有了,而插件中的apply方法會在開始編譯時依次被調用,並且傳入Compiler對象(後面會深入介紹),然後調用Compiler.run()開始編譯。
瞭解Compiler 和 Compilation
- 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();
});
常用api
讀取輸出資源、代碼塊、模塊及其依賴
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();
});
為了監聽 HTML 文件的變化,我們需要把 HTML 文件加入到依賴列表中,可以怎麼做:
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();
});
判斷webpack使用了哪些插件
// 判斷當前配置使用使用了 ExtractTextPlugin,
// compiler 參數即為 Webpack 在 apply(compiler) 中傳入的參數
function hasExtractTextPlugin(compiler) {
// 當前配置所有使用的插件列表
const plugins = compiler.options.plugins;
// 去 plugins 中尋找有沒有 ExtractTextPlugin 的實例
return plugins.find(plugin=>plugin.__proto__.constructor === ExtractTextPlugin) != null;
}
對於上面常用api的講解,我們可以知道compiler和compilation在plugin中占據著舉足輕重的作用,那麼具體它們長什麼樣子的,我們編寫個例子列印出來看看,下麵以extract-text-webpack-plugin
插件進行斷點調試的截圖,可以來看看這兩個分別列印出來的東西,
總結
一般情況下,我們是不需要去寫Plugin
,但是有時候我們有些業務需求是沒有插件可以滿足的,那麼我們便得需要自己去寫Plugin
,那瞭解Plugin
的一些相關知識點就是有必要的,我們不一定要每個鉤子或是API都相當熟,但是我們需要思路,瞭解如何編寫Plugin
,也是有必要的,Plugin
中最重要的compiler和compilation,一個Plugin
插件也就是圍繞著這個去擴展,對應詳細內容可以去webpack官網瞭解,compiler鏈接,compilation鏈接。