淺談Webpack4 plugins 實現(xiàn)原理
前言
在 wabpack 中核心功能除了 loader 應(yīng)該就是 plugins 插件了,它是在webpack執(zhí)行過程中會廣播一系列事件,plugin 會監(jiān)聽這些事件并通過 webpack Api 對輸出文件做對應(yīng)的處理, 如 hmlt-webpack-plugin 就是對模板魔劍 index.html 進(jìn)行拷貝到 dist 目錄的
認(rèn)識
先來通過源碼來認(rèn)識一下 plugins 的基本結(jié)構(gòu)
https://github.com/webpack/webpack/blob/webpack-4/lib/Compiler.js 551行
// 創(chuàng)建一個編譯器
createChildCompiler(
compilation,
compilerName,
compilerIndex,
outputOptions,
plugins // 里邊就有包含插件
) {
// new 一個 編譯器
const childCompiler = new Compiler(this.context);
// 尋找存在的所有 plugins 插件
if (Array.isArray(plugins)) {
for (const plugin of plugins) {
// 如果存在, 就調(diào)用 plugin 的 apply 方法
plugin.apply(childCompiler);
}
}
// 遍歷尋找 plugin 對應(yīng)的 hooks
for (const name in this.hooks) {
if (
![
"make",
"compile",
"emit",
"afterEmit",
"invalid",
"done",
"thisCompilation"
].includes(name)
) {
// 找到對應(yīng)的 hooks 并調(diào)用,
if (childCompiler.hooks[name]) {
childCompiler.hooks[name].taps = this.hooks[name].taps.slice();
}
}
}
// .... 省略 ....
return childCompiler;
}
通過上述源碼可以看出來 plugin 本質(zhì)就是一個類, 首先就是 new 一個 compiler 類,傳入當(dāng)前的上下文,然后判斷是否存在,存在則直接調(diào)用對應(yīng) plugin 的 apply 方法,然后再找到對應(yīng) plugin 調(diào)用的 hooks 事件流 , 發(fā)射給對應(yīng) hooks 事件
hooks 哪里來的 ?
https://github.com/webpack/webpack/blob/webpack-4/lib/Compiler.js 42行
// 上述的 Compiler 類繼承自 Tapable 類,而 Tapable 就定義了這些 hooks 事件流
class Compiler extends Tapable {
constructor(context) {
super();
this.hooks = {
/** @type {SyncBailHook<Compilation>} */
shouldEmit: new SyncBailHook(["compilation"]),
/** @type {AsyncSeriesHook<Stats>} */
done: new AsyncSeriesHook(["stats"]),
/** @type {AsyncSeriesHook<>} */
additionalPass: new AsyncSeriesHook([]),
/** @type {AsyncSeriesHook<Compiler>} */
beforeRun: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compiler>} */
run: new AsyncSeriesHook(["compiler"]),
/** @type {AsyncSeriesHook<Compilation>} */
emit: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<string, Buffer>} */
assetEmitted: new AsyncSeriesHook(["file", "content"]),
/** @type {AsyncSeriesHook<Compilation>} */
afterEmit: new AsyncSeriesHook(["compilation"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
thisCompilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<Compilation, CompilationParams>} */
compilation: new SyncHook(["compilation", "params"]),
/** @type {SyncHook<NormalModuleFactory>} */
normalModuleFactory: new SyncHook(["normalModuleFactory"]),
/** @type {SyncHook<ContextModuleFactory>} */
contextModuleFactory: new SyncHook(["contextModulefactory"]),
/** @type {AsyncSeriesHook<CompilationParams>} */
beforeCompile: new AsyncSeriesHook(["params"]),
/** @type {SyncHook<CompilationParams>} */
compile: new SyncHook(["params"]),
/** @type {AsyncParallelHook<Compilation>} */
make: new AsyncParallelHook(["compilation"]),
/** @type {AsyncSeriesHook<Compilation>} */
afterCompile: new AsyncSeriesHook(["compilation"]),
/** @type {AsyncSeriesHook<Compiler>} */
watchRun: new AsyncSeriesHook(["compiler"]),
/** @type {SyncHook<Error>} */
failed: new SyncHook(["error"]),
/** @type {SyncHook<string, string>} */
invalid: new SyncHook(["filename", "changeTime"]),
/** @type {SyncHook} */
watchClose: new SyncHook([]),
/** @type {SyncBailHook<string, string, any[]>} */
infrastructureLog: new SyncBailHook(["origin", "type", "args"]),
// TODO the following hooks are weirdly located here
// TODO move them for webpack 5
/** @type {SyncHook} */
environment: new SyncHook([]),
/** @type {SyncHook} */
afterEnvironment: new SyncHook([]),
/** @type {SyncHook<Compiler>} */
afterPlugins: new SyncHook(["compiler"]),
/** @type {SyncHook<Compiler>} */
afterResolvers: new SyncHook(["compiler"]),
/** @type {SyncBailHook<string, Entry>} */
entryOption: new SyncBailHook(["context", "entry"])
};
// TODO webpack 5 remove this
this.hooks.infrastructurelog = this.hooks.infrastructureLog;
// 通過 tab 調(diào)用對應(yīng)的 comiler 編譯器,并傳入一個回調(diào)函數(shù)
this._pluginCompat.tap("Compiler", options => {
switch (options.name) {
case "additional-pass":
case "before-run":
case "run":
case "emit":
case "after-emit":
case "before-compile":
case "make":
case "after-compile":
case "watch-run":
options.async = true;
break;
}
});
// 下方省略 ......
}
好了,了解過基本的結(jié)構(gòu)之后,就可以推理出 plugin 基本的結(jié)構(gòu)和用法了,就是下邊這樣
// 定義一個 plugins 類
class MyPlugins {
// 上邊有說 new 一個編譯器實例,會執(zhí)行實例的 apply 方法,傳入對應(yīng)的 comiler 實例
apply (compiler) {
// 調(diào)用 new 出來 compiler 實例下的 hooks 事件流,通過 tab 觸發(fā),并接收一個回調(diào)函數(shù)
compiler.hooks.done.tap('一般為插件昵稱', (默認(rèn)接收參數(shù)) => {
console.log('進(jìn)入執(zhí)行體');
})
}
}
// 導(dǎo)出
module.exports = MyPlugins
ok, 以上就是一個簡單的 模板 ,我們來試試內(nèi)部的鉤子函數(shù),是否會如愿以償?shù)谋徽{(diào)用和觸發(fā)
配置 webpack
let path = require('path')
let DonePlugin = require('./plugins/DonePlugins')
let AsyncPlugins = require('./plugins/AsyncPlugins')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new DonePlugin(), // 內(nèi)部同步 hooks
new AsyncPlugins() // 內(nèi)部異步 hooks
]
}
同步 plugin 插件模擬調(diào)用
class DonePlugins {
apply (compiler) {
compiler.hooks.done.tap('DonePlugin', (stats) => {
console.log('執(zhí)行: 編譯完成');
})
}
}
module.exports = DonePlugins
異步 plugin 插件模擬調(diào)用
class AsyncPlugins {
apply (compiler) {
compiler.hooks.emit.tapAsync('AsyncPlugin', (complete, callback) => {
setTimeout(() => {
console.log('執(zhí)行:文件發(fā)射出來');
callback()
}, 1000)
})
}
}
module.exports = AsyncPlugins
最后編譯 webpack 可以看到編譯控制臺,分別打印 執(zhí)行: 編譯完成,執(zhí)行:文件發(fā)射出來,說明這樣是可以調(diào)用到 hooks 事件流的,并且可以觸發(fā)。
實踐出真知
了解過基本結(jié)構(gòu)和使用的方式了,現(xiàn)在來手寫一個 plugin 插件,嗯,就來一個文件說明插件吧,我們?nèi)粘4虬梢源虬粋€ xxx.md 文件到 dist 目錄,來做一個打包說明,就來是實現(xiàn)這么一個小功能
文件說明插件
class FileListPlugin {
// 初始化,獲取文件的名稱
constructor ({filename}) {
this.filename = filename
}
// 同樣的模板形式,定義 apply 方法
apply (compiler) {
compiler.hooks.emit.tap('FileListPlugin', (compilation) => {
// assets 靜態(tài)資源,可以打印出 compilation 參數(shù),還有很多方法和屬性
let assets = compilation.assets;
// 定義輸出文檔結(jié)構(gòu)
let content = `## 文件名 資源大小\r\n`
// 遍歷靜態(tài)資源,動態(tài)組合輸出內(nèi)容
Object.entries(assets).forEach(([filename, stateObj]) => {
content += `- ${filename} ${stateObj.size()}\r\n`
})
// 輸出資源對象
assets[this.filename] = {
source () {
return content;
},
size () {
return content.length
}
}
})
}
}
// 導(dǎo)出
module.exports = FileListPlugin
webpack 配置
let path = require('path')
let HtmlWebpackPlugin = require('html-webpack-plugin')
// plugins 目錄與node_modules 同級, 自定義 plugins , 與 loader 類似
let FileListPlugin = require('./plugins/FileListPlugin')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'build.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html'
}),
new FileListPlugin({
filename: 'list.md'
})
]
}
ok,通過以上配置,我們再打包的時候就可以看到,每次打包在 dist 目錄就會出現(xiàn)一個 xxx.md 文件,而這個文件的內(nèi)容就是我們上邊的 content
到此這篇關(guān)于淺談Webpack4 plugins 實現(xiàn)原理的文章就介紹到這了,更多相關(guān)Webpack4 plugins 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springMvc 前端用json的方式向后臺傳遞對象數(shù)組方法
今天小編就為大家分享一篇springMvc 前端用json的方式向后臺傳遞對象數(shù)組方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
微信小程序?qū)崿F(xiàn)手機獲取驗證碼倒計時60s
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)手機獲取驗證碼后倒計時60s,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05
JavaScript常見的跨標(biāo)簽頁通信方式總結(jié)
跨標(biāo)簽頁通信是指在瀏覽器中的不同標(biāo)簽頁之間進(jìn)行數(shù)據(jù)傳遞和通信的過程,這篇文章為大家整理了前端常見的跨標(biāo)簽頁通信方式,有需要的小伙伴可以了解下2023-10-10
用JS提交參數(shù)創(chuàng)建form表單在FireFox中遇到的問題
在一個前端頁面上,需要通過JavaScript來提交參數(shù),使用JS創(chuàng)建form表單,將參數(shù)append到表單中進(jìn)行提交,接下來將介紹如何操作與實現(xiàn)2013-01-01
bootstrap table實現(xiàn)橫向合并與縱向合并
這篇文章主要為大家詳細(xì)介紹了bootstrap table實現(xiàn)橫向合并與縱向合并,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-07-07
js apply/call/caller/callee/bind使用方法與區(qū)別分析
js apply/call/caller/callee/bind使用方法與區(qū)別分析,需要的朋友可以參考下。2009-10-10
Bootstrap開發(fā)實戰(zhàn)之第一次接觸Bootstrap
Bootstrap開發(fā)實戰(zhàn)之第一次接觸Bootstrap,想要學(xué)好一門語言,首先應(yīng)該進(jìn)行深入了解,感興趣的小伙伴們可以參考一下2016-06-06
JavaScript實現(xiàn)鼠標(biāo)滾輪控制頁面圖片切換功能示例
這篇文章主要介紹了JavaScript實現(xiàn)鼠標(biāo)滾輪控制頁面圖片切換功能,涉及javascript事件響應(yīng)及頁面元素動態(tài)操作相關(guān)實現(xiàn)技巧,需要的朋友可以參考下2017-10-10

