淺談Webpack4 plugins 實(shí)現(xiàn)原理
前言
在 wabpack 中核心功能除了 loader 應(yīng)該就是 plugins 插件了,它是在webpack執(zhí)行過(guò)程中會(huì)廣播一系列事件,plugin 會(huì)監(jiān)聽(tīng)這些事件并通過(guò) webpack Api 對(duì)輸出文件做對(duì)應(yīng)的處理, 如 hmlt-webpack-plugin 就是對(duì)模板魔劍 index.html 進(jìn)行拷貝到 dist 目錄的
認(rèn)識(shí)
先來(lái)通過(guò)源碼來(lái)認(rèn)識(shí)一下 plugins 的基本結(jié)構(gòu)
https://github.com/webpack/webpack/blob/webpack-4/lib/Compiler.js 551行
// 創(chuàng)建一個(gè)編譯器 createChildCompiler( compilation, compilerName, compilerIndex, outputOptions, plugins // 里邊就有包含插件 ) { // new 一個(gè) 編譯器 const childCompiler = new Compiler(this.context); // 尋找存在的所有 plugins 插件 if (Array.isArray(plugins)) { for (const plugin of plugins) { // 如果存在, 就調(diào)用 plugin 的 apply 方法 plugin.apply(childCompiler); } } // 遍歷尋找 plugin 對(duì)應(yīng)的 hooks for (const name in this.hooks) { if ( ![ "make", "compile", "emit", "afterEmit", "invalid", "done", "thisCompilation" ].includes(name) ) { // 找到對(duì)應(yīng)的 hooks 并調(diào)用, if (childCompiler.hooks[name]) { childCompiler.hooks[name].taps = this.hooks[name].taps.slice(); } } } // .... 省略 .... return childCompiler; }
通過(guò)上述源碼可以看出來(lái) plugin 本質(zhì)就是一個(gè)類(lèi), 首先就是 new 一個(gè) compiler 類(lèi),傳入當(dāng)前的上下文,然后判斷是否存在,存在則直接調(diào)用對(duì)應(yīng) plugin 的 apply 方法,然后再找到對(duì)應(yīng) plugin 調(diào)用的 hooks 事件流 , 發(fā)射給對(duì)應(yīng) hooks 事件
hooks 哪里來(lái)的 ?
https://github.com/webpack/webpack/blob/webpack-4/lib/Compiler.js 42行
// 上述的 Compiler 類(lèi)繼承自 Tapable 類(lèi),而 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; // 通過(guò) tab 調(diào)用對(duì)應(yīng)的 comiler 編譯器,并傳入一個(gè)回調(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; } }); // 下方省略 ...... }
好了,了解過(guò)基本的結(jié)構(gòu)之后,就可以推理出 plugin 基本的結(jié)構(gòu)和用法了,就是下邊這樣
// 定義一個(gè) plugins 類(lèi) class MyPlugins { // 上邊有說(shuō) new 一個(gè)編譯器實(shí)例,會(huì)執(zhí)行實(shí)例的 apply 方法,傳入對(duì)應(yīng)的 comiler 實(shí)例 apply (compiler) { // 調(diào)用 new 出來(lái) compiler 實(shí)例下的 hooks 事件流,通過(guò) tab 觸發(fā),并接收一個(gè)回調(diào)函數(shù) compiler.hooks.done.tap('一般為插件昵稱(chēng)', (默認(rèn)接收參數(shù)) => { console.log('進(jìn)入執(zhí)行體'); }) } } // 導(dǎo)出 module.exports = MyPlugins
ok, 以上就是一個(gè)簡(jiǎn)單的 模板 ,我們來(lái)試試內(nèi)部的鉤子函數(shù),是否會(huì)如愿以?xún)數(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ā)射出來(lái)'); callback() }, 1000) }) } } module.exports = AsyncPlugins
最后編譯 webpack 可以看到編譯控制臺(tái),分別打印 執(zhí)行: 編譯完成,執(zhí)行:文件發(fā)射出來(lái),說(shuō)明這樣是可以調(diào)用到 hooks 事件流的,并且可以觸發(fā)。
實(shí)踐出真知
了解過(guò)基本結(jié)構(gòu)和使用的方式了,現(xiàn)在來(lái)手寫(xiě)一個(gè) plugin 插件,嗯,就來(lái)一個(gè)文件說(shuō)明插件吧,我們?nèi)粘4虬梢源虬粋€(gè) xxx.md 文件到 dist 目錄,來(lái)做一個(gè)打包說(shuō)明,就來(lái)是實(shí)現(xiàn)這么一個(gè)小功能
文件說(shuō)明插件
class FileListPlugin { // 初始化,獲取文件的名稱(chēng) 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)資源,動(dòng)態(tài)組合輸出內(nèi)容 Object.entries(assets).forEach(([filename, stateObj]) => { content += `- ${filename} ${stateObj.size()}\r\n` }) // 輸出資源對(duì)象 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 同級(jí), 自定義 plugins , 與 loader 類(lèi)似 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,通過(guò)以上配置,我們?cè)俅虬臅r(shí)候就可以看到,每次打包在 dist 目錄就會(huì)出現(xiàn)一個(gè) xxx.md 文件,而這個(gè)文件的內(nèi)容就是我們上邊的 content
到此這篇關(guān)于淺談Webpack4 plugins 實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)Webpack4 plugins 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript中防抖和節(jié)流的實(shí)戰(zhàn)應(yīng)用記錄
防抖與節(jié)流都是用來(lái)限制用戶(hù)頻發(fā)觸發(fā)事件的機(jī)制,下面這篇文章主要給大家介紹了關(guān)于JavaScript中防抖和節(jié)流的實(shí)戰(zhàn)應(yīng)用,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-04-04javascript innerText和innerHtml應(yīng)用
innerText和innerHtml看字面也應(yīng)該理解的了2010-01-01Javascript實(shí)現(xiàn)圖片輪播效果(二)圖片序列節(jié)點(diǎn)的控制實(shí)現(xiàn)
這篇文章主要介紹了Javascript實(shí)現(xiàn)圖片輪播效果(二)圖片序列節(jié)點(diǎn)的控制實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2016-02-02JS解決回調(diào)地獄為什么需要Promise來(lái)優(yōu)化異步編程
這篇文章主要為大家介紹了JS解決回調(diào)地獄為什么需要Promise來(lái)優(yōu)化異步編程原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10用js+iframe形成頁(yè)面的一種遮罩效果的具體實(shí)現(xiàn)
用js形成頁(yè)面的一種遮罩效果,選擇想要進(jìn)行遮罩的窗口,在這里想要遮罩的是一個(gè)iframe窗口,具體的實(shí)現(xiàn)如下,感興趣的朋友可以參參考下2013-12-12ES6 Set結(jié)構(gòu)的應(yīng)用實(shí)例分析
這篇文章主要介紹了ES6 Set結(jié)構(gòu)的應(yīng)用,結(jié)合實(shí)例形式分析了ES6 set結(jié)構(gòu)的功能、特點(diǎn)、常見(jiàn)用法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2019-06-06js 計(jì)算圖片內(nèi)點(diǎn)個(gè)數(shù)的示例代碼
這篇文章主要介紹了js 計(jì)算圖片內(nèi)點(diǎn)個(gè)數(shù)的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04