淺談Webpack4 plugins 實現(xiàn)原理
前言
在 wabpack 中核心功能除了 loader 應該就是 plugins 插件了,它是在webpack執(zhí)行過程中會廣播一系列事件,plugin 會監(jiān)聽這些事件并通過 webpack Api 對輸出文件做對應的處理, 如 hmlt-webpack-plugin 就是對模板魔劍 index.html 進行拷貝到 dist 目錄的
認識
先來通過源碼來認識一下 plugins 的基本結構
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 對應的 hooks
for (const name in this.hooks) {
if (
![
"make",
"compile",
"emit",
"afterEmit",
"invalid",
"done",
"thisCompilation"
].includes(name)
) {
// 找到對應的 hooks 并調(diào)用,
if (childCompiler.hooks[name]) {
childCompiler.hooks[name].taps = this.hooks[name].taps.slice();
}
}
}
// .... 省略 ....
return childCompiler;
}
通過上述源碼可以看出來 plugin 本質(zhì)就是一個類, 首先就是 new 一個 compiler 類,傳入當前的上下文,然后判斷是否存在,存在則直接調(diào)用對應 plugin 的 apply 方法,然后再找到對應 plugin 調(diào)用的 hooks 事件流 , 發(fā)射給對應 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)用對應的 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;
}
});
// 下方省略 ......
}
好了,了解過基本的結構之后,就可以推理出 plugin 基本的結構和用法了,就是下邊這樣
// 定義一個 plugins 類
class MyPlugins {
// 上邊有說 new 一個編譯器實例,會執(zhí)行實例的 apply 方法,傳入對應的 comiler 實例
apply (compiler) {
// 調(diào)用 new 出來 compiler 實例下的 hooks 事件流,通過 tab 觸發(fā),并接收一個回調(diào)函數(shù)
compiler.hooks.done.tap('一般為插件昵稱', (默認接收參數(shù)) => {
console.log('進入執(zhí)行體');
})
}
}
// 導出
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ā)。
實踐出真知
了解過基本結構和使用的方式了,現(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;
// 定義輸出文檔結構
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
}
}
})
}
}
// 導出
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
到此這篇關于淺談Webpack4 plugins 實現(xiàn)原理的文章就介紹到這了,更多相關Webpack4 plugins 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
JavaScript中防抖和節(jié)流的實戰(zhàn)應用記錄
防抖與節(jié)流都是用來限制用戶頻發(fā)觸發(fā)事件的機制,下面這篇文章主要給大家介紹了關于JavaScript中防抖和節(jié)流的實戰(zhàn)應用,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-04-04
javascript innerText和innerHtml應用
innerText和innerHtml看字面也應該理解的了2010-01-01
Javascript實現(xiàn)圖片輪播效果(二)圖片序列節(jié)點的控制實現(xiàn)
這篇文章主要介紹了Javascript實現(xiàn)圖片輪播效果(二)圖片序列節(jié)點的控制實現(xiàn)的相關資料,需要的朋友可以參考下2016-02-02
JS解決回調(diào)地獄為什么需要Promise來優(yōu)化異步編程
這篇文章主要為大家介紹了JS解決回調(diào)地獄為什么需要Promise來優(yōu)化異步編程原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10
用js+iframe形成頁面的一種遮罩效果的具體實現(xiàn)
用js形成頁面的一種遮罩效果,選擇想要進行遮罩的窗口,在這里想要遮罩的是一個iframe窗口,具體的實現(xiàn)如下,感興趣的朋友可以參參考下2013-12-12

