編寫 Loader
Loader就像是一個翻譯員,能把源文件經(jīng)過轉(zhuǎn)化后輸出新的結(jié)果,并且一個文件還可以鏈式的經(jīng)過多個翻譯員翻譯。
以處理SCSS文件為例:
- SCSS源代碼會先交給
sass-loader把SCSS轉(zhuǎn)換成CSS; - 把
sass-loader輸出的CSS交給css-loader處理,找出CSS中依賴的資源、壓縮CSS等; - 把
css-loader輸出的CSS交給style-loader處理,轉(zhuǎn)換成通過腳本加載的JavaScript代碼;
可以看出以上的處理過程需要有順序的鏈式執(zhí)行,先sass-loader再css-loader再style-loader。 以上處理的Webpack相關(guān)配置如下:
module.exports = {
module: {
rules: [
{
// 增加對 SCSS 文件的支持
test: /\.scss/,
// SCSS 文件的處理順序為先 sass-loader 再 css-loader 再 style-loader
use: [
'style-loader',
{
loader:'css-loader',
// 給 css-loader 傳入配置項
options:{
minimize:true,
}
},
'sass-loader'],
},
]
},
};
Loader的職責(zé)
由上面的例子可以看出:一個Loader的職責(zé)是單一的,只需要完成一種轉(zhuǎn)換。 如果一個源文件需要經(jīng)歷多步轉(zhuǎn)換才能正常使用,就通過多個Loader去轉(zhuǎn)換。 在調(diào)用多個Loader去轉(zhuǎn)換一個文件時,每個Loader會鏈式的順序執(zhí)行, 第一個Loader將會拿到需處理的原內(nèi)容,上一個Loader處理后的結(jié)果會傳給下一個接著處理,最后的Loader將處理后的最終結(jié)果返回給Webpack。
所以,在你開發(fā)一個Loader時,請保持其職責(zé)的單一性,你只需關(guān)心輸入和輸出。
Loader基礎(chǔ)
由于Webpack是運行在Node.js之上的,一個Loader其實就是一個Node.js模塊,這個模塊需要導(dǎo)出一個函數(shù)。 這個導(dǎo)出的函數(shù)的工作就是獲得處理前的原內(nèi)容,對原內(nèi)容執(zhí)行處理后,返回處理后的內(nèi)容。
一個最簡單的Loader的源碼如下:
module.exports = function(source) {
// source 為 compiler 傳遞給 Loader 的一個文件的原內(nèi)容
// 該函數(shù)需要返回處理后的內(nèi)容,這里簡單起見,直接把原內(nèi)容返回了,相當(dāng)于該`Loader`沒有做任何轉(zhuǎn)換
return source;
};
由于Loader運行在Node.js中,你可以調(diào)用任何Node.js自帶的API,或者安裝第三方模塊進行調(diào)用:
const sass = require('node-sass');
module.exports = function(source) {
return sass(source);
};
Loader進階
Webpack還提供一些API供Loader調(diào)用。
獲得Loader的options
在最上面處理SCSS文件的Webpack配置中,給css-loader傳了options參數(shù),以控制css-loader。要在自己編寫的Loader中獲取到用戶傳入的options,需要這樣做:
const loaderUtils = require('loader-utils');
module.exports = function(source) {
// 獲取到用戶給當(dāng)前 Loader 傳入的 options
const options = loaderUtils.getOptions(this);
return source;
};
返回其它結(jié)果
上面的Loader都只是返回了原內(nèi)容轉(zhuǎn)換后的內(nèi)容,但有些場景下還需要返回除了內(nèi)容之外的東西。
例如以用babel-loader轉(zhuǎn)換ES6代碼為例,它還需要輸出轉(zhuǎn)換后的ES5代碼對應(yīng)的Source Map,以方便調(diào)試源碼。 為了把Source Map也一起隨著ES5代碼返回給Webpack,可以這樣寫:
module.exports = function(source) {
// 通過 this.callback 告訴 Webpack 返回的結(jié)果
this.callback(null, source, sourceMaps);
// 當(dāng)你使用 this.callback 返回內(nèi)容時,該 Loader 必須返回 undefined,
// 以讓 Webpack 知道該 Loader 返回的結(jié)果在 this.callback 中,而不是 return 中
return;
};
其中的this.callback是Webpack給Loader注入的API,以方便Loader和Webpack之間通信。this.callback的詳細使用方法如下:
this.callback(
// 當(dāng)無法轉(zhuǎn)換原內(nèi)容時,給 Webpack 返回一個 Error
err: Error | null,
// 原內(nèi)容轉(zhuǎn)換后的內(nèi)容
content: string | Buffer,
// 用于把轉(zhuǎn)換后的內(nèi)容得出原內(nèi)容的 Source Map,方便調(diào)試
sourceMap?: SourceMap,
// 如果本次轉(zhuǎn)換為原內(nèi)容生成了 AST 語法樹,可以把這個 AST 返回,
// 以方便之后需要 AST 的 Loader 復(fù)用該 AST,以避免重復(fù)生成 AST,提升性能
abstractSyntaxTree?: AST
);
Source Map的生成很耗時,通常在開發(fā)環(huán)境下才會生成Source Map,其它環(huán)境下不用生成,以加速構(gòu)建。 為此Webpack為Loader提供了this.sourceMap API去告訴Loader當(dāng)前構(gòu)建環(huán)境下用戶是否需要Source Map。
同步與異步
Loader有同步和異步之分,上面介紹的Loader都是同步的Loader,因為它們的轉(zhuǎn)換流程都是同步的,轉(zhuǎn)換完成后再返回結(jié)果。 但在有些場景下轉(zhuǎn)換的步驟只能是異步完成的,例如你需要通過網(wǎng)絡(luò)請求才能得出結(jié)果,如果采用同步的方式網(wǎng)絡(luò)請求就會阻塞整個構(gòu)建,導(dǎo)致構(gòu)建非常緩慢。
在轉(zhuǎn)換步驟是異步時,你可以這樣:
module.exports = function(source) {
// 告訴 Webpack 本次轉(zhuǎn)換是異步的,Loader 會在 callback 中回調(diào)結(jié)果
var callback = this.async();
someAsyncOperation(source, function(err, result, sourceMaps, ast) {
// 通過 callback 返回異步執(zhí)行后的結(jié)果
callback(err, result, sourceMaps, ast);
});
};
處理二進制數(shù)據(jù)
在默認的情況下,Webpack傳給Loader的原內(nèi)容都是UTF-8格式編碼的字符串。 但有些場景下Loader不是處理文本文件,而是處理二進制文件,例如file-loader,就需要Webpack給Loader傳入二進制格式的數(shù)據(jù)。 為此,你需要這樣編寫Loader:
module.exports = function(source) {
// 在 exports.raw === true 時,Webpack 傳給 Loader 的 source 是 Buffer 類型的
source instanceof Buffer === true;
// Loader 返回的類型也可以是 Buffer 類型的
// 在 exports.raw !== true 時,Loader 也可以返回 Buffer 類型的結(jié)果
return source;
};
// 通過 exports.raw 屬性告訴 Webpack 該 Loader 是否需要二進制數(shù)據(jù)
module.exports.raw = true;
以上代碼中最關(guān)鍵的代碼是最后一行module.exports.raw = true;,沒有該行Loader只能拿到字符串。
緩存加速
在有些情況下,有些轉(zhuǎn)換操作需要大量計算非常耗時,如果每次構(gòu)建都重新執(zhí)行重復(fù)的轉(zhuǎn)換操作,構(gòu)建將會變得非常緩慢。為此,Webpack會默認緩存所有Loader的處理結(jié)果,也就是說在需要被處理的文件或者其依賴的文件沒有發(fā)生變化時, 是不會重新調(diào)用對應(yīng)的Loader去執(zhí)行轉(zhuǎn)換操作的。
如果想讓W(xué)ebpack不緩存該Loader的處理結(jié)果,可以這樣:
module.exports = function(source) {
// 關(guān)閉該 Loader 的緩存功能
this.cacheable(false);
return source;
};
其它Loader API
除了以上提到的在Loader中能調(diào)用的Webpack API外,還存在以下常用API:
-
this.context:當(dāng)前處理文件的所在目錄,假如當(dāng)前Loader處理的文件是/src/main.js,則this.context就等于/src。 -
this.resource:當(dāng)前處理文件的完整請求路徑,包括querystring,例如/src/main.js?name=1。 -
this.resourcePath:當(dāng)前處理文件的路徑,例如/src/main.js。 -
this.resourceQuery:當(dāng)前處理文件的querystring。 -
this.target:等于Webpack配置中的Target。 -
this.loadModule:當(dāng)Loader在處理一個文件時,如果依賴其它文件的處理結(jié)果才能得出當(dāng)前文件的結(jié)果時, 就可以通過this.loadModule(request: string, callback: function(err, source, sourceMap, module))去獲得request對應(yīng)文件的處理結(jié)果。 -
this.resolve:像require語句一樣獲得指定文件的完整路徑,使用方法為resolve(context: string, request: string, callback: function(err, result: string))。 -
this.addDependency:給當(dāng)前處理文件添加其依賴的文件,以便再其依賴的文件發(fā)生變化時,會重新調(diào)用Loader處理該文件。使用方法為addDependency(file: string)。 -
this.addContextDependency:和addDependency類似,但addContextDependency是把整個目錄加入到當(dāng)前正在處理文件的依賴中。使用方法為addContextDependency(directory: string)。 -
this.clearDependencies:清除當(dāng)前正在處理文件的所有依賴,使用方法為clearDependencies()。 -
this.emitFile:輸出一個文件,使用方法為emitFile(name: string, content: Buffer|string, sourceMap: {...})。
加載本地Loader
在開發(fā)Loader的過程中,為了測試編寫的Loader是否能正常工作,需要把它配置到Webpack中后,才可能會調(diào)用該Loader。使用的Loader都是通過Npm安裝的,要使用Loader時會直接使用Loader的名稱,代碼如下:
module.exports = {
module: {
rules: [
{
test: /\.css/,
use: ['style-loader'],
},
]
},
};
如果還采取以上的方法去使用本地開發(fā)的Loader將會很麻煩,因為你需要確保編寫的Loader的源碼是在node_modules目錄下。 為此你需要先把編寫的Loader發(fā)布到Npm倉庫后再安裝到本地項目使用。
解決以上問題的便捷方法有兩種,分別如下:
Npm link
Npm link專門用于開發(fā)和調(diào)試本地Npm模塊,能做到在不發(fā)布模塊的情況下,把本地的一個正在開發(fā)的模塊的源碼鏈接到項目的node_modules目錄下,讓項目可以直接使用本地的Npm模塊。 由于是通過軟鏈接的方式實現(xiàn)的,編輯了本地的Npm模塊代碼,在項目中也能使用到編輯后的代碼。
完成Npm link的步驟如下:
- 確保正在開發(fā)的本地Npm模塊(也就是正在開發(fā)的Loader)的
package.json已經(jīng)正確配置好; - 在本地Npm模塊根目錄下執(zhí)行
npm link,把本地模塊注冊到全局; - 在項目根目錄下執(zhí)行
npm link loader-name,把第2步注冊到全局的本地Npm模塊鏈接到項目的node_moduels下,其中的loader-name是指在第1步中的package.json文件中配置的模塊名稱。
鏈接好Loader到項目后你就可以像使用一個真正的 Npm 模塊一樣使用本地的Loader了。
ResolveLoader
ResolveLoader用于配置Webpack如何尋找Loader。 默認情況下只會去node_modules目錄下尋找,為了讓W(xué)ebpack加載放在本地項目中的Loader需要修改resolveLoader.modules。
假如本地的Loader在項目目錄中的./loaders/loader-name中,則需要如下配置:
module.exports = {
resolveLoader:{
// 去哪些目錄下尋找 Loader,有先后順序之分
modules: ['node_modules','./loaders/'],
}
}
加上以上配置后,Webpack會先去node_modules項目下尋找Loader,如果找不到,會再去./loaders/目錄下尋找。
實戰(zhàn)
接下來從實際出發(fā),來編寫一個解決實際問題的Loader。
該Loader名叫comment-require-loader,作用是把JavaScript代碼中的注釋語法
// @require '../style/index.css'
轉(zhuǎn)換成
require('../style/index.css');
該Loader的使用方法如下:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: ['comment-require-loader'],
// 針對采用了 fis3 CSS 導(dǎo)入語法的 JavaScript 文件通過 comment-require-loader 去轉(zhuǎn)換
include: [path.resolve(__dirname, 'node_modules/imui')]
}
]
}
};
該Loader的實現(xiàn)非常簡單,完整代碼如下:
function replace(source) {
// 使用正則把 // @require '../style/index.css' 轉(zhuǎn)換成 require('../style/index.css');
return source.replace(/(\/\/ *@require) +(('|").+('|")).*/, 'require($2);');
}
module.exports = function (content) {
return replace(content);
};
編寫Plugin
Webpack通過Plugin機制讓其更加靈活,以適應(yīng)各種應(yīng)用場景。 在Webpack運行的生命周期中會廣播出許多事件,Plugin可以監(jiān)聽這些事件,在合適的時機通過Webpack提供的API改變輸出結(jié)果。
一個最基礎(chǔ)的Plugin的代碼是這樣的:
class BasicPlugin{
// 在構(gòu)造函數(shù)中獲取用戶給該插件傳入的配置
constructor(options){
}
// Webpack 會調(diào)用 BasicPlugin 實例的 apply 方法給插件實例傳入 compiler 對象
apply(compiler){
compiler.plugin('compilation',function(compilation) {
})
}
}
// 導(dǎo)出 Plugin
module.exports = BasicPlugin;
在使用這個Plugin時,相關(guān)配置代碼如下:
const BasicPlugin = require('./BasicPlugin.js');
module.export = {
plugins:[
new BasicPlugin(options),
]
}
Webpack啟動后,在讀取配置的過程中會先執(zhí)行new BasicPlugin(options)初始化一個BasicPlugin獲得其實例。 在初始化compiler對象后,再調(diào)用basicPlugin.apply(compiler)給插件實例傳入compiler對象。 插件實例在獲取到compiler對象后,就可以通過compiler.plugin(事件名稱, 回調(diào)函數(shù))監(jiān)聽到Webpack廣播出來的事件。 并且可以通過compiler對象去操作Webpack。
Compiler和Compilation
在開發(fā)Plugin時最常用的兩個對象就是Compiler和Compilation,它們是Plugin和Webpack之間的橋梁。Compiler和Compilation的含義如下:
-
Compiler對象包含了Webpack環(huán)境所有的的配置信息,包含options,loaders,plugins這些信息,這個對象在Webpack啟動時候被實例化,它是全局唯一的,可以簡單地把它理解為Webpack實例; -
Compilation對象包含了當(dāng)前的模塊資源、編譯生成資源、變化的文件等。當(dāng)Webpack以開發(fā)模式運行時,每當(dāng)檢測到一個文件變化,一次新的Compilation將被創(chuàng)建。Compilation對象也提供了很多事件回調(diào)供插件做擴展。通過Compilation也能讀取到Compiler對象。
Compiler和Compilation的區(qū)別在于:Compiler代表了整個Webpack從啟動到關(guān)閉的生命周期,而Compilation只是代表了一次新的編譯。
事件流
Webpack就像一條生產(chǎn)線,要經(jīng)過一系列處理流程后才能將源文件轉(zhuǎn)換成輸出結(jié)果。 這條生產(chǎn)線上的每個處理流程的職責(zé)都是單一的,多個流程之間有存在依賴關(guān)系,只有完成當(dāng)前處理后才能交給下一個流程去處理。 插件就像是一個插入到生產(chǎn)線中的一個功能,在特定的時機對生產(chǎn)線上的資源做處理。
Webpack通過Tapable來組織這條復(fù)雜的生產(chǎn)線。 Webpack在運行過程中會廣播事件,插件只需要監(jiān)聽它所關(guān)心的事件,就能加入到這條生產(chǎn)線中,去改變生產(chǎn)線的運作。 Webpack的事件流機制保證了插件的有序性,使得整個系統(tǒng)擴展性很好。
Webpack的事件流機制應(yīng)用了觀察者模式,和Node.js中的EventEmitter非常相似。Compiler和Compilation都繼承自Tapable,可以直接在Compiler和Compilation對象上廣播和監(jiān)聽事件,方法如下:
/**
* 廣播出事件
* event-name 為事件名稱,注意不要和現(xiàn)有的事件重名
* params 為附帶的參數(shù)
*/
compiler.apply('event-name',params);
/**
* 監(jiān)聽名稱為 event-name 的事件,當(dāng) event-name 事件發(fā)生時,函數(shù)就會被執(zhí)行。
* 同時函數(shù)中的 params 參數(shù)為廣播事件時附帶的參數(shù)。
*/
compiler.plugin('event-name',function(params) {
});
同理,compilation.apply和compilation.plugin使用方法和上面一致。
在開發(fā)插件時,你可能會不知道該如何下手,因為你不知道該監(jiān)聽哪個事件才能完成任務(wù)。
在開發(fā)插件時,還需要注意以下兩點:
- 只要能拿到
Compiler或Compilation對象,就能廣播出新的事件,所以在新開發(fā)的插件中也能廣播出事件,給其它插件監(jiān)聽使用。 - 傳給每個插件的
Compiler和Compilation對象都是同一個引用。也就是說在一個插件中修改了Compiler或Compilation對象上的屬性,會影響到后面的插件。 - 有些事件是異步的,這些異步的事件會附帶兩個參數(shù),第二個參數(shù)為回調(diào)函數(shù),在插件處理完任務(wù)時需要調(diào)用回調(diào)函數(shù)通知Webpack,才會進入下一處理流程。例如:
compiler.plugin('emit',function(compilation, callback) {
// 支持處理邏輯
// 處理完畢后執(zhí)行 callback 以通知 Webpack
// 如果不執(zhí)行 callback,運行流程將會一直卡在這不往下執(zhí)行
callback();
});
常用API
插件可以用來修改輸出文件、增加輸出文件、甚至可以提升Webpack性能、等等,總之插件通過調(diào)用 Webpack提供的API能完成很多事情。 由于Webpack提供的API非常多,有很多API很少用的上,又加上篇幅有限,下面來介紹一些常用的API。
讀取輸出資源、代碼塊、模塊及其依賴
有些插件可能需要讀取Webpack的處理結(jié)果,例如輸出資源、代碼塊、模塊及其依賴,以便做下一步處理。
在emit事件發(fā)生時,代表源文件的轉(zhuǎn)換和組裝已經(jīng)完成,在這里可以讀取到最終將輸出的資源、代碼塊、模塊及其依賴,并且可以修改輸出資源的內(nèi)容。 插件代碼如下:
class Plugin {
apply(compiler) {
compiler.plugin('emit', function (compilation, callback) {
// compilation.chunks 存放所有代碼塊,是一個數(shù)組
compilation.chunks.forEach(function (chunk) {
// chunk 代表一個代碼塊
// 代碼塊由多個模塊組成,通過 chunk.forEachModule 能讀取組成代碼塊的每個模塊
chunk.forEachModule(function (module) {
// module 代表一個模塊
// module.fileDependencies 存放當(dāng)前模塊的所有依賴的文件路徑,是一個數(shù)組
module.fileDependencies.forEach(function (filepath) {
});
});
// Webpack 會根據(jù) Chunk 去生成輸出的文件資源,每個 Chunk 都對應(yīng)一個及其以上的輸出文件
// 例如在 Chunk 中包含了 CSS 模塊并且使用了 ExtractTextPlugin 時,
// 該 Chunk 就會生成 .js 和 .css 兩個文件
chunk.files.forEach(function (filename) {
// compilation.assets 存放當(dāng)前所有即將輸出的資源
// 調(diào)用一個輸出資源的 source() 方法能獲取到輸出資源的內(nèi)容
let source = compilation.assets[filename].source();
});
});
// 這是一個異步事件,要記得調(diào)用 callback 通知 Webpack 本次事件監(jiān)聽處理結(jié)束。
// 如果忘記了調(diào)用 callback,Webpack 將一直卡在這里而不會往后執(zhí)行。
callback();
})
}
}
監(jiān)聽文件變化
Webpack會從配置的入口模塊出發(fā),依次找出所有的依賴模塊,當(dāng)入口模塊或者其依賴的模塊發(fā)生變化時, 就會觸發(fā)一次新的Compilation。
在開發(fā)插件時經(jīng)常需要知道是哪個文件發(fā)生變化導(dǎo)致了新的Compilation,為此可以使用如下代碼:
// 當(dāng)依賴的文件發(fā)生變化時會觸發(fā) watch-run 事件
compiler.plugin('watch-run', (watching, callback) => {
// 獲取發(fā)生變化的文件列表
const changedFiles = watching.compiler.watchFileSystem.watcher.mtimes;
// changedFiles 格式為鍵值對,鍵為發(fā)生變化的文件路徑。
if (changedFiles[filePath] !== undefined) {
// filePath 對應(yīng)的文件發(fā)生了變化
}
callback();
});
默認情況下Webpack只會監(jiān)視入口和其依賴的模塊是否發(fā)生變化,在有些情況下項目可能需要引入新的文件,例如引入一個HTML文件。 由于 JavaScript 文件不會去導(dǎo)入HTML文件,Webpack就不會監(jiān)聽HTML文件的變化,編輯HTML文件時就不會重新觸發(fā)新的Compilation。 為了監(jiān)聽HTML文件的變化,我們需要把HTML文件加入到依賴列表中,為此可以使用如下代碼:
compiler.plugin('after-compile', (compilation, callback) => {
// 把 HTML 文件添加到文件依賴列表,好讓 Webpack 去監(jiān)聽 HTML 模塊文件,在 HTML 模版文件發(fā)生變化時重新啟動一次編譯
compilation.fileDependencies.push(filePath);
callback();
});
修改輸出資源
有些場景下插件需要修改、增加、刪除輸出的資源,要做到這點需要監(jiān)聽emit事件,因為發(fā)生emit事件時所有模塊的轉(zhuǎn)換和代碼塊對應(yīng)的文件已經(jīng)生成好, 需要輸出的資源即將輸出,因此emit事件是修改Webpack輸出資源的最后時機。
所有需要輸出的資源會存放在compilation.assets中,compilation.assets是一個鍵值對,鍵為需要輸出的文件名稱,值為文件對應(yīng)的內(nèi)容。
設(shè)置compilation.assets的代碼如下:
compiler.plugin('emit', (compilation, callback) => {
// 設(shè)置名稱為 fileName 的輸出資源
compilation.assets[fileName] = {
// 返回文件內(nèi)容
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];
// 獲取輸出資源的內(nèi)容
asset.source();
// 獲取輸出資源的文件大小
asset.size();
callback();
});
判斷Webpack使用了哪些插件
在開發(fā)一個插件時可能需要根據(jù)當(dāng)前配置是否使用了其它某個插件而做下一步?jīng)Q定,因此需要讀取Webpack當(dāng)前的插件配置情況。 以判斷當(dāng)前是否使用了ExtractTextPlugin為例,可以使用如下代碼:
// 判斷當(dāng)前配置使用使用了 ExtractTextPlugin,
// compiler 參數(shù)即為 Webpack 在 apply(compiler) 中傳入的參數(shù)
function hasExtractTextPlugin(compiler) {
// 當(dāng)前配置所有使用的插件列表
const plugins = compiler.options.plugins;
// 去 plugins 中尋找有沒有 ExtractTextPlugin 的實例
return plugins.find(plugin=>plugin.__proto__.constructor === ExtractTextPlugin) != null;
}
實戰(zhàn)
下面我們?nèi)崿F(xiàn)一個插件。
該插件的名稱取名叫EndWebpackPlugin,作用是在Webpack即將退出時再附加一些額外的操作,例如在Webpack成功編譯和輸出了文件后執(zhí)行發(fā)布操作把輸出的文件上傳到服務(wù)器。 同時該插件還能區(qū)分Webpack構(gòu)建是否執(zhí)行成功。使用該插件時方法如下:
module.exports = {
plugins:[
// 在初始化 EndWebpackPlugin 時傳入了兩個參數(shù),分別是在成功時的回調(diào)函數(shù)和失敗時的回調(diào)函數(shù);
new EndWebpackPlugin(() => {
// Webpack 構(gòu)建成功,并且文件輸出了后會執(zhí)行到這里,在這里可以做發(fā)布文件操作
}, (err) => {
// Webpack 構(gòu)建失敗,err 是導(dǎo)致錯誤的原因
console.error(err);
})
]
}
要實現(xiàn)該插件,需要借助兩個事件:
-
done:在成功構(gòu)建并且輸出了文件后,Webpack即將退出時發(fā)生; -
failed:在構(gòu)建出現(xiàn)異常導(dǎo)致構(gòu)建失敗,Webpack即將退出時發(fā)生;
實現(xiàn)該插件非常簡單,完整代碼如下:
class EndWebpackPlugin {
constructor(doneCallback, failCallback) {
// 存下在構(gòu)造函數(shù)中傳入的回調(diào)函數(shù)
this.doneCallback = doneCallback;
this.failCallback = failCallback;
}
apply(compiler) {
compiler.plugin('done', (stats) => {
// 在 done 事件中回調(diào) doneCallback
this.doneCallback(stats);
});
compiler.plugin('failed', (err) => {
// 在 failed 事件中回調(diào) failCallback
this.failCallback(err);
});
}
}
// 導(dǎo)出插件
module.exports = EndWebpackPlugin;
從開發(fā)這個插件可以看出,找到合適的事件點去完成功能在開發(fā)插件時顯得尤為重要。Webpack在運行過程中廣播出常用事件,你可以從中找到你需要的事件。