摘要:執(zhí)行完成后會返回如下圖的結(jié)果,根據(jù)返回數(shù)據(jù)把源碼和存儲在的屬性上的回調(diào)函數(shù)中調(diào)用類生成,并根據(jù)生成依賴后回調(diào)方法返回類。
webpack設(shè)計模式 一切資源皆Module
Module(模塊)是webpack的中的關(guān)鍵實體。Webpack 會從配置的 Entry 開始遞歸找出所有依賴的模塊. 通過Loaders(模塊轉(zhuǎn)換器),用于把模塊原內(nèi)容按照需求轉(zhuǎn)換成新模塊內(nèi)容.
事件驅(qū)動架構(gòu)webpack整體是一個事件驅(qū)動架構(gòu),所有的功能都以Plugin(插件)的方式集成在構(gòu)建流程中,通過發(fā)布訂閱事件來觸發(fā)各個插件執(zhí)行。webpack核心使用tapable來實現(xiàn)Plugin(插件)的注冊和調(diào)用,Tapable是一個事件發(fā)布(tap)訂閱(call)庫
概念Graph 模塊之間的Dependency(依賴關(guān)系)構(gòu)成的依賴圖
Compiler (Tapable實例)訂閱了webpack最頂層的生命周期事件
Complilation (Tapable實例)該對象由Compiler創(chuàng)建, 負責構(gòu)建Graph,Seal,Render...是整個工作流程的核心生命周期,包含Dep Graph 遍歷算法,優(yōu)化(optimize),tree shaking...
Compiler 和 Compilation 的區(qū)別在于:Compiler 代表了整個 Webpack 從啟動到關(guān)閉的生命周期,而 Compilation 只是代表了一次新的編譯。
Resolver(Tapable實例)資源路徑解析器
ModuleFactory (Tapable實例) 被Resolver成功解析的資源需要被這個工廠類被實例化成Module
Parser (Tapable實例) 負責將Module(ModuleFactory實例化來的)轉(zhuǎn)AST的解析器 (webpack 默認用acorn),并解析出不同規(guī)范的require/import 轉(zhuǎn)成Dependency(依賴)
Template 模塊化的模板. Chunk,Module,Dependency都有各自的模塊模板,來自各自的工廠類的實例
bundle和 chunk區(qū)別:https://github.com/webpack/we...
bundle:由多個不同的模塊打包生成生成最終的js文件,一個js文件即是1個bundle。
chunk: Graph的組成部分。一般有n個入口=n個bundle=graph中有n個chunk。但假設(shè)由于n個入口有m個公共模塊會被重復打包,需要分離,最終=n+m個bundle=graph中有n+m個chunk
有3類chunk:
Entry chunk: 包含runtime code 的,就是開發(fā)模式下編譯出的有很長的/******/的部分 (是bundle)
Initial chunk:同步加載,不包含runtime code 的。(可能和entry chunk打包成一個bundle,也可能分離成多個bundle)
Normal chunk:延遲加載/異步 的module
chunk的依賴圖算法
https://medium.com/webpack/th...
Compiler 讀取配置,創(chuàng)建Compilation
Compiler創(chuàng)建Graph的過程:
Compilation讀取資源入口
NMF(normal module factory)
Resolver 解析
輸出NM
Parser 解析 AST
js json 用acorn
其他用Loader (執(zhí)行l(wèi)oader runner)
如果有依賴, 重復步驟 2
Compilation優(yōu)化Graph
Compilation渲染Graph
根據(jù)Graph上的各類模塊用各自的Template渲染
chunk template
Dependency template
...
合成IIFE的最終資源
Tapable 鉤子列表鉤子名 | 執(zhí)行方式 | 要點 |
---|---|---|
SyncHook | 同步串行 | 不關(guān)心監(jiān)聽函數(shù)的返回值 |
SyncBailHook | 同步串行 | 只要監(jiān)聽函數(shù)中有一個函數(shù)的返回值不為null,則跳過剩下所有的邏輯 |
SyncWaterfallHook | 同步串行 | 上一個監(jiān)聽函數(shù)的返回值可以傳給下一個監(jiān)聽函數(shù) |
SyncLoopHook | 同步循環(huán) | 當監(jiān)聽函數(shù)被觸發(fā)的時候,如果該監(jiān)聽函數(shù)返回true時則這個監(jiān)聽函數(shù)會反復執(zhí)行,如果返回undefined則表示退出循環(huán) |
AsyncParallelHook | 異步并發(fā) | 不關(guān)心監(jiān)聽函數(shù)的返回值 |
AsyncParallelBailHook | 異步并發(fā) | 只要監(jiān)聽函數(shù)的返回值不為null,就會忽略后面的監(jiān)聽函數(shù)執(zhí)行,直接跳躍到callAsync等觸發(fā)函數(shù)綁定的回調(diào)函數(shù),然后執(zhí)行這個被綁定的回調(diào)函數(shù) |
AsyncSeriesHook | 異步串行 | 不關(guān)心callback的參數(shù) |
AsyncSeriesBailHook | 異步串行 | callback()的參數(shù)不為null,就會直接執(zhí)行callAsync等觸發(fā)函數(shù)綁定的回調(diào)函數(shù) |
AsyncSeriesWaterfalllHook | 異步串行 | 上一個監(jiān)聽函數(shù)中的callback(err,data)的第二個參數(shù),可以作為下一個監(jiān)聽函數(shù)的參數(shù) |
//創(chuàng)建一個發(fā)布訂閱中心 let Center=new TapableHook() //注冊監(jiān)聽事件 Center.tap("eventName",callback) //觸發(fā)事件 Center.call(...args) //注冊攔截器 Center.intercept({ context,//事件回調(diào)和攔截器的共享數(shù)據(jù) call:()=>{},//鉤子觸發(fā)前 register:()=>{},//添加事件時 tap:()=>{},//執(zhí)行鉤子前 loop:()=>{},//循環(huán)鉤子 })
更多示例 https://juejin.im/post/5abf33...
Module它有很多子類:RawModule, NormalModule ,MultiModule,ContextModule,DelegatedModule,DllModule,ExternalModule 等
ModuleFactory: 使用工廠模式創(chuàng)建不同的Module,有四個主要的子類: NormalModuleFactory,ContextModuleFactory , DllModuleFactory,MultiModuleFactory
Template
mainTemplate 和 chunkTemplate
if(chunk.entry) { source = this.mainTemplate.render(this.hash, chunk, this.moduleTemplate, this.dependencyTemplates); } else { source = this.chunkTemplate.render(chunk, this.moduleTemplate, this.dependencyTemplates); }
不同模塊規(guī)范封裝
MainTemplate.prototype.requireFn = "__webpack_require__"; MainTemplate.prototype.render = function(hash, chunk, moduleTemplate, dependencyTemplates) { var buf = []; // 每一個module都有一個moduleId,在最后會替換。 buf.push("function " + this.requireFn + "(moduleId) {"); buf.push(this.indent(this.applyPluginsWaterfall("require", "", chunk, hash))); buf.push("}"); buf.push(""); ... // 其余封裝操作 };
ModuleTemplate 是對所有模塊進行一個代碼生成
HotUpdateChunkTemplate 是對熱替換模塊的一個處理
webpack_requirefunction __webpack_require__(moduleId) { // 1.首先會檢查模塊緩存 if(installedModules[moduleId]) { return installedModules[moduleId].exports; } // 2. 緩存不存在時,創(chuàng)建并緩存一個新的模塊對象,類似Node中的new Module操作 var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {}, children: [] }; // 3. 執(zhí)行模塊,類似于Node中的: // result = compiledWrapper.call(this.exports, this.exports, require, this, filename, dirname); modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); //需要引入模塊時,同步地將模塊從暫存區(qū)取出來執(zhí)行,避免使用網(wǎng)絡(luò)請求導致過長的同步等待時間。 module.l = true; // 4. 返回該module的輸出 return module.exports; }異步模塊加載
__webpack_require__.e = function requireEnsure(chunkId) { var promises = []; var installedChunkData = installedChunks[chunkId]; // 判斷該chunk是否已經(jīng)被加載,0表示已加載。installChunk中的狀態(tài): // undefined:chunk未進行加載, // null:chunk preloaded/prefetched // Promise:chunk正在加載中 // 0:chunk加載完畢 if(installedChunkData !== 0) { // chunk不為null和undefined,則為Promise,表示加載中,繼續(xù)等待 if(installedChunkData) { promises.push(installedChunkData[2]); } else { // 注意這里installChunk的數(shù)據(jù)格式 // 從左到右三個元素分別為resolve、reject、promise var promise = new Promise(function(resolve, reject) { installedChunkData = installedChunks[chunkId] = [resolve, reject]; }); promises.push(installedChunkData[2] = promise); // 下面代碼主要是根據(jù)chunkId加載對應的script腳本 var head = document.getElementsByTagName("head")[0]; var script = document.createElement("script"); var onScriptComplete; script.charset = "utf-8"; script.timeout = 120; if (__webpack_require__.nc) { script.setAttribute("nonce", __webpack_require__.nc); } // jsonpScriptSrc方法會根據(jù)傳入的chunkId返回對應的文件路徑 script.src = jsonpScriptSrc(chunkId); onScriptComplete = function (event) { script.onerror = script.onload = null; clearTimeout(timeout); var chunk = installedChunks[chunkId]; if(chunk !== 0) { if(chunk) { var errorType = event && (event.type === "load" ? "missing" : event.type); var realSrc = event && event.target && event.target.src; var error = new Error("Loading chunk " + chunkId + " failed. (" + errorType + ": " + realSrc + ")"); error.type = errorType; error.request = realSrc; chunk[1](error); } installedChunks[chunkId] = undefined; } }; var timeout = setTimeout(function(){ onScriptComplete({ type: "timeout", target: script }); }, 120000); script.onerror = script.onload = onScriptComplete; head.appendChild(script); } } return Promise.all(promises); };異步模塊緩存
// webpack runtime chunk function webpackJsonpCallback(data) { var chunkIds = data[0]; var moreModules = data[1]; var executeModules = data[2]; var moduleId, chunkId, i = 0, resolves = []; // webpack會在installChunks中存儲chunk的載入狀態(tài),據(jù)此判斷chunk是否加載完畢 for(;i < chunkIds.length; i++) { chunkId = chunkIds[i]; if(installedChunks[chunkId]) { resolves.push(installedChunks[chunkId][0]); } installedChunks[chunkId] = 0; } // 注意,這里會進行“注冊”,將模塊暫存入內(nèi)存中 // 將module chunk中第二個數(shù)組元素包含的 module 方法注冊到 modules 對象里 for(moduleId in moreModules) { if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; } } if(parentJsonpFunction) parentJsonpFunction(data); //先根據(jù)模塊注冊時的chunkId,取出installedChunks對應的所有l(wèi)oading中的chunk,最后將這些chunk的promise進行resolve操作 while(resolves.length) { resolves.shift()(); } deferredModules.push.apply(deferredModules, executeModules || []); return checkDeferredModules(); };保證chunk加載后才執(zhí)行模塊
function checkDeferredModules() { var result; for(var i = 0; i < deferredModules.length; i++) { var deferredModule = deferredModules[i]; var fulfilled = true; // 第一個元素是模塊id,后面是其所需的chunk for(var j = 1; j < deferredModule.length; j++) { var depId = deferredModule[j]; // 這里會首先判斷模塊所需chunk是否已經(jīng)加載完畢 if(installedChunks[depId] !== 0) fulfilled = false; } // 只有模塊所需的chunk都加載完畢,該模塊才會被執(zhí)行(__webpack_require__) if(fulfilled) { deferredModules.splice(i--, 1); result = __webpack_require__(__webpack_require__.s = deferredModule[0]); } } return result; }Module 被 Loader 編譯的主要步驟
webpack的配置options
在Compiler.js中會為將用戶配置與默認配置(WebpackOptionsDefaulter)合并,其中就包括了loader的默認配置 module.defaultRules (OptionsDefaulter則是一個封裝的配置項存取器,封裝了一些特殊的方法來操作配置對象)
//lib/webpack.js options = new WebpackOptionsDefaulter().process(options); compiler = new Compiler(options.context); compiler.options = options; /*options:{ entry: {},//入口配置 output: {}, //輸出配置 plugins: [], //插件集合(配置文件 + shell指令) module: { loaders: [ [Object] ] }, //模塊配置 context: //工程路徑 ... }*/
創(chuàng)建Module
根據(jù)配置創(chuàng)建Module的工廠類Factory(Compiler.js)
通過loader的resolver來解析loader路徑
使用Factory創(chuàng)建 NormalModule實例
使用loaderResolver解析loader模塊路徑
根據(jù)rule.modules創(chuàng)建RulesSet規(guī)則集
Loader編譯過程(詳見Loader章節(jié))
NormalModule實例.build() 進行模塊的構(gòu)建
loader-runner 執(zhí)行編譯module
CompilerCompiler源碼
compiler.hooksclass Compiler extends Tapable { constructor(context) { super(); this.hooks = { shouldEmit: new SyncBailHook(["compilation"]),//此時返回 true/false。 done: new AsyncSeriesHook(["stats"]),//編譯(compilation)完成。 additionalPass: new AsyncSeriesHook([]), beforeRun: new AsyncSeriesHook(["compiler"]),//compiler.run() 執(zhí)行之前,添加一個鉤子。 run: new AsyncSeriesHook(["compiler"]),//開始讀取 records 之前,鉤入(hook into) compiler。 emit: new AsyncSeriesHook(["compilation"]),//輸出到dist目錄 afterEmit: new AsyncSeriesHook(["compilation"]),//生成資源到 output 目錄之后。 thisCompilation: new SyncHook(["compilation", "params"]),//觸發(fā) compilation 事件之前執(zhí)行(查看下面的 compilation)。 compilation: new SyncHook(["compilation", "params"]),//編譯(compilation)創(chuàng)建之后,執(zhí)行插件。 normalModuleFactory: new SyncHook(["normalModuleFactory"]),//NormalModuleFactory 創(chuàng)建之后,執(zhí)行插件。 contextModuleFactory: new SyncHook(["contextModulefactory"]),//ContextModuleFactory 創(chuàng)建之后,執(zhí)行插件。 beforeCompile: new AsyncSeriesHook(["params"]),//編譯(compilation)參數(shù)創(chuàng)建之后,執(zhí)行插件。 compile: new SyncHook(["params"]),//一個新的編譯(compilation)創(chuàng)建之后,鉤入(hook into) compiler。 make: new AsyncParallelHook(["compilation"]),//從入口分析依賴以及間接依賴模塊 afterCompile: new AsyncSeriesHook(["compilation"]),//完成構(gòu)建,緩存數(shù)據(jù) watchRun: new AsyncSeriesHook(["compiler"]),//監(jiān)聽模式下,一個新的編譯(compilation)觸發(fā)之后,執(zhí)行一個插件,但是是在實際編譯開始之前。 failed: new SyncHook(["error"]),//編譯(compilation)失敗。 invalid: new SyncHook(["filename", "changeTime"]),//監(jiān)聽模式下,編譯無效時。 watchClose: new SyncHook([]),//監(jiān)聽模式停止。 } } }compiler其他屬性
this.name /** @type {string=} */ this.parentCompilation /** @type {Compilation=} */ this.outputPath = /** @type {string} */ this.outputFileSystem this.inputFileSystem this.recordsInputPath /** @type {string|null} */ this.recordsOutputPath /** @type {string|null} */ this.records = {}; this.removedFiles //new Set(); this.fileTimestamps /** @type {Mapcompiler.prototype.run(callback)執(zhí)行過程} */ this.contextTimestamps /** @type {Map } */ this.resolverFactory /** @type {ResolverFactory} */ this.options = /** @type {WebpackOptions} */ this.context = context; this.requestShortener this.running = false;/** @type {boolean} */ this.watchMode = false;/** @type {boolean} */ this._assetEmittingSourceCache /** @private @type {WeakMap
compiler.hooks.beforeRun
compiler.hooks.run
compiler.compile
params=this.newCompilationParams 創(chuàng)建NormalModuleFactory,contextModuleFactory實例。
NMF.hooks.beforeResolve
NMF.hooks.resolve 解析loader模塊的路徑(例如css-loader這個loader的模塊路徑是什么)
NMF.hooks.factory 基于resolve鉤子的返回值來創(chuàng)建NormalModule實例。
NMF.hooks.afterResolve
NMF.hooks.createModule
compiler.hooks.compile.call(params)
compilation = new Compilation(compiler)
this.hooks.thisCompilation.call(compilation, params)
this.hooks.compilation.call(compilation, params)
compiler.hooks.make
compilation.hooks.finish
compilation.hooks.seal
compiler.hooks.afterCompile
return callback(null, compilation)
Compilation源碼
Compilation 對象包含了當前的模塊資源、編譯生成資源、變化的文件等。當 Webpack 以開發(fā)模式運行時,每當檢測到一個文件變化,一次新的 Compilation 將被創(chuàng)建。Compilation 對象也提供了很多事件回調(diào)供插件做擴展。通過 Compilation 也能讀取到 Compiler 對象。
承接上文的compilation = new Compilation(compiler)
負責組織整個打包過程,包含了每個構(gòu)建環(huán)節(jié)及輸出環(huán)節(jié)所對應的方法
如 addEntry() , _addModuleChain() , buildModule() , seal() , createChunkAssets() (在每一個節(jié)點都會觸發(fā) webpack 事件去調(diào)用各插件)。
該對象內(nèi)部存放著所有 module ,chunk,生成的 asset 以及用來生成最后打包文件的 template 的信息。
compilation.addEntry()主要執(zhí)行過程
comilation._addModuleChain()
moduleFactory = comilation.dependencyFactories.get(Dep)
moduleFactory.create()
comilation.addModule(module)
comilation.buildModule(module)
afterBuild()
compilation.seal()主要執(zhí)行過程comilation.hooks.optimizeDependencies
創(chuàng)建chunks
循環(huán) comilation.chunkGroups.push(entrypoint)
comilation.processDependenciesBlocksForChunkGroups(comilation.chunkGroups.slice())
comilation.sortModules(comilation.modules);
優(yōu)化modules
comilation.hooks.optimizeModules
優(yōu)化chunks
comilation.hooks.optimizeChunks
優(yōu)化tree
comilation.hooks.optimizeTree
comilation.hooks.optimizeChunkModules
comilation.sortItemsWithModuleIds
comilation.sortItemsWithChunkIds
comilation.createHash
comilation.createModuleAssets 添加到compildation.assets[fileName]
comilation.hooks.additionalChunkAssets
comilation.summarizeDependencies
comilation.hooks.additionalAssets
comilation.hooks.optimizeChunkAssets
comilation.hooks.optimizeAssets
comilation.hooks.afterSeal
Plugin插件可以用于執(zhí)行范圍更廣的任務(wù)。包括:打包優(yōu)化,資源管理,注入環(huán)境變量
plugin: 一個具有 apply 方法的 JavaScript 對象。apply 方法會被 compiler 調(diào)用,并且 compiler 對象可在整個編譯生命周期訪問。這些插件包通常以某種方式擴展編譯功能。
編寫Plugin示例class MyPlugin{ apply(compiler){ compiler.hooks.done.tabAsync("myPlugin",(stats,cb)=>{ const assetsNames=[] for(let assetName in stats.compilation.assets) assetNames.push(assetName) console.log(assetsNames.join(" ")) cb() }) compiler.hooks.compilation.tap("MyPlugin",(compilation,params)=>{ new MyCompilationPlugin().apply(compilation) }) } } class MyCompilationPlugin{ apply(compilation){ compilation.hooks.additionalAssets.tapAsync("MyPlugin", callback => { download("https://img.shields.io/npm/v/webpack.svg", function(resp) { if(resp.status === 200) { compilation.assets["webpack-version.svg"] = toAsset(resp); callback() } else callback(new Error("[webpack-example-plugin] Unable to download the image")) }); }); } } module.exports=MyPlugin
其他聲明周期hooks和示例 https://webpack.docschina.org...
Resolver在 NormalModuleFactory.js 的 resolver.resolve 中觸發(fā)
hooks在 WebpackOptionsApply.js的 compiler.resolverFactory.hooks中。
可以完全被替換,比如注入自己的fileSystem
Parser在 CommonJSPulgin.js的new CommonJsRequireDependencyParserPlugin(options).appply(parser)觸發(fā),調(diào)用 CommonJsRequireDependencyParserPlugin.js 的apply(parser),負責添加Dependency,Template...
hooks在 CommonJsPlugin.js的 normarlModuleFactory.hooks.parser中
Loader在make階段build中會調(diào)用doBuild去加載資源,doBuild中會傳入資源路徑和插件資源去調(diào)用loader-runner插件的runLoaders方法去加載和執(zhí)行l(wèi)oader。執(zhí)行完成后會返回如下圖的result結(jié)果,根據(jù)返回數(shù)據(jù)把源碼和sourceMap存儲在module的_source屬性上;doBuild的回調(diào)函數(shù)中調(diào)用Parser類生成AST,并根據(jù)AST生成依賴后回調(diào)buildModule方法返回compilation類。Loader的路徑
NormalModuleFactory將loader分為preLoader、postLoader和loader三種
對loader文件的路徑解析分為兩種:inline loader和config文件中的loader。
require的inline loader路徑前面的感嘆號作用:
! 禁用preLoaders (代碼檢查和測試,不生成module)
!! 禁用所有Loaders
-!禁用preLoaders和loaders,但不是postLoaders
前面提到NormalModuleFactory中的resolver鉤子中會先處理inline loader。
最終loader的順序:post、inline、normal和pre
然而loader是從右至左執(zhí)行的,真實的loader執(zhí)行順序是倒過來的,因此inlineLoader是整體后于config中normal loader執(zhí)行的。
路徑解析之 inline loader
正則解析loader和參數(shù)
//NormalModuleFactory.js let elements = requestWithoutMatchResource .replace(/^-?!+/, "") .replace(/!!+/g, "!") .split("!");
將“解析模塊的loader數(shù)組”與“解析模塊本身”一起并行執(zhí)行,用到了neo-async這個庫(和async庫類似,都是為異步編程提供一些工具方法,但是會比async庫更快。)
解析返回結(jié)果:
[ // 第一個元素是一個loader數(shù)組 [ { loader: "/workspace/basic-demo/home/node_modules/html-webpack-plugin/lib/loader.js", options: undefined } ], // 第二個元素是模塊本身的一些信息 { resourceResolveData: { context: [Object], path: "/workspace/basic-demo/home/public/index.html", request: undefined, query: "", module: false, file: false, descriptionFilePath: "/workspace/basic-demo/home/package.json", descriptionFileData: [Object], descriptionFileRoot: "/workspace/basic-demo/home", relativePath: "./public/index.html", __innerRequest_request: undefined, __innerRequest_relativePath: "./public/index.html", __innerRequest: "./public/index.html" }, resource: "/workspace/basic-demo/home/public/index.html" } ]路徑解析之 config loader
NormalModuleFactory中有一個ruleSet的屬性,相當于一個規(guī)則過濾器,會將resourcePath應用于所有的module.rules規(guī)則,它可以根據(jù)模塊路徑名,匹配出模塊所需的loader。webpack編譯會根據(jù)用戶配置與默認配置,實例化一個RuleSet,它包含:
類靜態(tài)方法normalizeRule() 將配置值轉(zhuǎn)換為標準化的test對象,其上還會存儲一個this.references屬性
實例方法exec() 每次創(chuàng)建一個新的NormalModule時都會調(diào)用RuleSet實例的.exec()方法,只有當通過了各類測試條件,才會將該loader push到結(jié)果數(shù)組中。
references {map} key是loader在配置中的類型和位置,例如,ref-2表示loader配置數(shù)組中的第三個。
pitch & normal同一匹配(test)資源有多l(xiāng)oader的時候:(類似先捕獲,再冒泡)
先順序loader.pitch()(源碼里是PitchingLoaders 不妨稱為 pitch 階段)
再倒序loader()(源碼里是NormalLoaders 不妨稱為 normal 階段).
這兩個階段(pitch和normal)就是loader-runner中對應的iteratePitchingLoaders()和iterateNormalLoaders()兩個方法。
如果某個 loader 在 pitch 方法中return結(jié)果,會跳過剩下的 loader。那么pitch的遞歸就此結(jié)束,開始從當前位置從后往前執(zhí)行normal
normal loaders 結(jié)果示例(apply-loader, pug-loader)//webpack.config.js test: /.pug/, use: [ "apply-loader", "pug-loader", ]
先執(zhí)行pug-loader,得到 Module pug-loader/index.js!./src/index.pug的js代碼:
var pug = __webpack_require__(/*! pug-runtime/index.js */ "pug-runtime/index.js"); function template(locals) {var pug_html = "", pug_mixins = {}, pug_interp;pug_html = pug_html + "u003Cdiv class="haha"u003Easdu003Cu002Fdivu003E";return pug_html;}; module.exports = template; //# sourceURL=webpack:///./src/index.pug?pug-loader
再執(zhí)行apply-loader,得到 Module "./src/index.pug" 的js代碼:
var req = __webpack_require__(/*! !pug-loader!./src/index.pug */ "pug-loader/index.js!./src/index.pug"); module.exports = (req["default"] || req).apply(req, []) //# sourceURL=webpack:///./src/index.pug?
此時假設(shè)在入口文件./src/index.js引用
var html =__webpack_require__( "./index.pug") console.log(html) //asd
這個入口文件 Module 的js代碼:
module.exports = __webpack_require__(/*! ./src/index.js */"./src/index.js"); //# sourceURL=webpack:///multi_./src/index.js?
build 后可看到控制臺輸出的 1個Chunk,2個Module(1個fs忽略),3個中間Module和一些隱藏Module
Asset Size Chunks Chunk Names main.js 12.9 KiB main [emitted] main Entrypoint main = main.js [0] multi ./src/index.js 28 bytes {main} [built] [1] fs (ignored) 15 bytes {main} [optional] [built] [pug-loader/index.js!./src/index.pug] pug-loader!./src/index.pug 288 bytes {main} [built] [./src/index.js] 51 bytes {main} [built] [./src/index.pug] 222 bytes {main} [built]pitching loaders 結(jié)果示例 (style-loader, css-loader)
pitch:順序執(zhí)行l(wèi)oader.pitch,例:
//webpack.config.js test: /.css/, use: [ "style-loader", "css-loader", ]
style-loader(負責添加到頁面)
得到Module ./src/a.css的js代碼:
// Load styles var content = __webpack_require__(/*! !css-loader/dist/cjs.js!./a.css */ "css-loader/dist/cjs.js!./src/a.css"); if(typeof content === "string") content = [[module.i, content, ""]]; // Transform styles var options = {"hmr":true} options.transform = undefined options.insertInto = undefined; // Add styles to the DOM var update = __webpack_require__(/*! style-loader/lib/addStyles.js */ "style-loader/lib/addStyles.js")(content, options); module.exports = content.locals; //# sourceURL=webpack:///./src/a.css?
build 后可看到控制臺輸出的 1個Chunk,1個最終Module,3個中間Module,和一些隱藏Module
Asset Size Chunks Chunk Names main.js 24.3 KiB main [emitted] main Entrypoint main = main.js [0] multi ./src/index.js 28 bytes {main} [built] [./node_modules/_css-loader@2.1.1@css-loader/dist/cjs.js!./src/a.css] 170 bytes {main} [built] [./src/a.css] 1.12 KiB {main} [built] [./src/index.js] 16 bytes {main} [built] + 3 hidden modules
其他loader解析:bundle loader , style-loader , css-loader , file-loader, url-loader
happypack
loader的內(nèi)部處理流程:流水線機制,即挨個處理每個loader,前一個loader的結(jié)果會傳遞給下一個loader。
loader有一些主要的特性:同步&異步; pitch&normal; context
runLoaders方法調(diào)用iteratePitchingLoaders去遞歸查找執(zhí)行有pich屬性的loader;若存在多個pitch屬性的loader則依次執(zhí)行所有帶pitch屬性的loader,執(zhí)行完后逆向執(zhí)行所有帶pitch屬性的normal的normal loader后返回result,沒有pitch屬性的loader就不會再執(zhí)行;若loaders中沒有pitch屬性的loader則逆向執(zhí)行l(wèi)oader;執(zhí)行正常loader是在iterateNormalLoaders方法完成的,處理完所有l(wèi)oader后返回result;
用 loader 編譯 Module 的主要步驟
compilation.addEntry()方法中調(diào)用的_addModuleChain()會執(zhí)行一系列的模塊方法,其中對于未build過的模塊,最終會調(diào)用到NormalModule.doBuild()方法。
loader中的this其實是一個叫loaderContext的對象
doBuild() run Loaders后將js代碼通過acorn轉(zhuǎn)為AST (源碼) Parser中生產(chǎn)AST語法樹后調(diào)用walkStatements方法分析語法樹,根據(jù)AST的node的type來遞歸查找每一個node的類型和執(zhí)行不同的邏輯,并創(chuàng)建依賴。
loadLoader.js 一個兼容性的模塊加載器
LoaderRunner.js 核心
runLoaders()
iteratePitchingLoaders() 遞歸執(zhí)行,并記錄loader的pitch狀態(tài);loaderIndex++;當達到最大的loader序號時,處理實際的module(源碼):
//遞歸執(zhí)行每個loader的pitch函數(shù),并在所有pitch執(zhí)行完后調(diào)用processResource if(loaderContext.loaderIndex >= loaderContext.loaders.length) return processResource(options, loaderContext, callback);
processResource() 將目標module當做loaderContext的一個依賴,添加該模塊為依賴和讀取模塊內(nèi)容
iterateNormalLoaders()遞歸執(zhí)行normal,和pitch的流程大同小異,需要注意的是順序是反過來的,從后往前。,loaderIndex--
在pitch中返回值除了跳過余下loader外,不僅會阻止.addDependency()觸發(fā)(不將該模塊資源添加進依賴),而且無法讀取模塊的文件內(nèi)容。loader會將pitch返回的值作為“文件內(nèi)容”來處理,并返回給webpack。
pitch 與loader本身方法的執(zhí)行順序
runSyncOrAsync() pitch與normal的實際執(zhí)行 (源碼)
往context上添加了async和callback函數(shù).
當我們編寫loader調(diào)用this.async()或this.callback()時,會將loader變?yōu)橐粋€異步的loader,并返回一個異步回調(diào),還可以直接返回一個Promise。
只有isSync標識為true時,才會在loader function執(zhí)行完畢后立即(同步)回調(diào)callback來繼續(xù)loader-runner。
Loader的this對象(LoaderContext)屬性清單version:number 2//版本 emitWarning(warning: Error)//發(fā)出一個警告 emitError(error: Error)//發(fā)出一個錯誤 resolve(context: String, request: String, callback: function(err, result: string)),//像 require 表達式一樣解析一個 request getResolve(),//? emitFile(name: string, content: Buffer|string, sourceMap: {...}),//產(chǎn)生一個文件 rootContext:"/home/seasonley/workplace/webpack-demo",//從 webpack 4 開始,原先的 this.options.context 被改進為 this.rootContext webpack:true,//如果是由 webpack 編譯的,這個布爾值會被設(shè)置為真(loader 最初被設(shè)計為可以同時當 Babel transform 用) sourceMap:false,//是否生成source map _module:[Object:NormalModule], _compilation:[Object:Compilation], _compiler:[Object:Compiler], fs:[Object:CachedInputFileSystem],//用于訪問 compilation 的 inputFileSystem 屬性。 target:"web",//編譯的目標。從配置選項中傳遞過來的。示例:"web", "node" loadModule(request: string, callback: function(err, source, sourceMap, module))],//解析給定的 request 到一個模塊,應用所有配置的 loader ,并且在回調(diào)函數(shù)中傳入生成的 source 、sourceMap 和 模塊實例(通常是 NormalModule 的一個實例)。如果你需要獲取其他模塊的源代碼來生成結(jié)果的話,你可以使用這個函數(shù)。 context: "/home/seasonley/workplace/webpack-demo/src",//模塊所在的目錄??梢杂米鹘馕銎渌K路徑的上下文。 loaderIndex: 0,//當前 loader 在 loader 數(shù)組中的索引。 loaders:Array [ { path: "/home/seasonley/workplace/webpack-demo/src/myloader.js", query: "", options: undefined, ident: undefined, normal: [Function], pitch: undefined, raw: undefined, data: null, pitchExecuted: true, normalExecuted: true, request: [Getter/Setter] } ],//所有 loader 組成的數(shù)組。它在 pitch 階段的時候是可以寫入的。 resourcePath: "/home/seasonley/workplace/webpack-demo/src/index.js",//資源文件的路徑。 resourceQuery: "",//資源的 query 參數(shù)。 async(),//告訴 loader-runner 這個 loader 將會異步地回調(diào)。返回 this.callback。 callback(err,content,sourceMap,meta),/*一個可以同步或者異步調(diào)用的可以返回多個結(jié)果的函數(shù)。如果這個函數(shù)被調(diào)用的話,你應該返回 undefined 從而避免含糊的 loader 結(jié)果。 this.callback( err: Error | null, content: string | Buffer, sourceMap?: SourceMap, meta?: any ); 可以將抽象語法樹AST(例如 ESTree)作為第四個參數(shù)(meta),如果你想在多個 loader 之間共享通用的 AST,這樣做有助于加速編譯時間。*/ cacheable(flag),/*設(shè)置是否可緩存標志的函數(shù): cacheable(flag = true: boolean) 默認情況下,loader 的處理結(jié)果會被標記為可緩存。調(diào)用這個方法然后傳入 false,可以關(guān)閉 loader 的緩存。 一個可緩存的 loader 在輸入和相關(guān)依賴沒有變化時,必須返回相同的結(jié)果。這意味著 loader 除了 this.addDependency 里指定的以外,不應該有其它任何外部依賴。*/ addDependency(file),//加入一個文件作為產(chǎn)生 loader 結(jié)果的依賴,使它們的任何變化可以被監(jiān)聽到。例如,html-loader 就使用了這個技巧,當它發(fā)現(xiàn) src 和 src-set 屬性時,就會把這些屬性上的 url 加入到被解析的 html 文件的依賴中。 dependency(file),// addDependency的簡寫 addContextDependency(directory),//(directory: string)把文件夾作為 loader 結(jié)果的依賴加入。 getDependencies(),// getContextDependencies(),// clearDependencies(),//移除 loader 結(jié)果的所有依賴。甚至自己和其它 loader 的初始依賴??紤]使用 pitch。 resource: [Getter/Setter],//request 中的資源部分,包括 query 參數(shù)。示例:"/abc/resource.js?rrr" request: [Getter],/*被解析出來的 request 字符串。"/abc/loader1.js?xyz!/abc/node_modules/loader2/index.js!/abc/resource.js?rrr"*/ remainingRequest: [Getter],// currentRequest: [Getter],// previousRequest: [Getter],// query: [Getter],/** 如果這個 loader 配置了 options 對象的話,this.query 就指向這個 option 對象。 如果 loader 中沒有 options,而是以 query 字符串作為參數(shù)調(diào)用時,this.query 就是一個以 ? 開頭的字符串。 使用 loader-utils 中提供的 getOptions 方法 來提取給定 loader 的 option。*/ data: [Getter]//在 pitch 階段和正常階段之間共享的 data 對象。 /* Object.defineProperty(loaderContext, "data", { enumerable: true, get: function() { return loaderContext.loaders[loaderContext.loaderIndex].data; } }); */編寫Loader
function myLoader(resource) { if(/.js/.test(this.resource)) return resource+";console.log(`wa js`);"; }; module.exports = myLoader
//webpack.config.js var path = require("path"); module.exports = { mode: "production", entry: ["./src/index.js"], output: { path: path.resolve(__dirname, "./dist"), filename: "[name].js" }, module: { rules: [ { test: /index.js$/, use: "bundle-loader" } ] }, resolveLoader: { modules: ["./src/myloader/"], } };webpack源碼分析方法
inspect-brk 啟動的時候自動在第一行自動加上斷點
node --inspect-brk ./node_modules/webpack/bin/webpack.js --config ./webpack.config.js
chrome輸入 chrome://inspect/
Tree Shakingwebpack 通過靜態(tài)語法分析,找出了不用的 export ,把他們改成 free variable(只是把 exports 關(guān)鍵字刪除了,變量的聲明并沒有刪除)
Uglify通過靜態(tài)語法分析,找出了不用的變量聲明,直接把他們刪了。
Watch webpack-dev-server當配置了watch時webpack-dev-middleware 將 webpack 原本的 outputFileSystem 替換成了MemoryFileSystem(memory-fs 插件) 實例。
MemoryFileSystem 是個抽象的文件系統(tǒng)庫,webpack將該部分解耦,可進一步設(shè)置redis或mongodb作為文件系統(tǒng),在多個webpack實例中共享資源
監(jiān)控當執(zhí)行watch時會實例化一個Watching對象,監(jiān)控和構(gòu)建打包都是Watching實例來控制;在Watching構(gòu)造函數(shù)中設(shè)置變化延遲通知時間(默認200),然后調(diào)用_go方法;webpack首次構(gòu)建和后續(xù)的文件變化重新構(gòu)建都是_執(zhí)行_go方法,在__go方法中調(diào)用this.compiler.compile啟動編譯。webpack構(gòu)建完成后會觸發(fā) _done方法,在 _done方法中調(diào)用this.watch方法,傳入compilation.fileDependencies和compilation.contextDependencies需要監(jiān)控的文件夾和目錄;在watch中調(diào)用this.compiler.watchFileSystem.watch方法正式開始創(chuàng)建監(jiān)聽。
Watchpack在this.compiler.watchFileSystem.watch中每次會重新創(chuàng)建一個Watchpack實例,創(chuàng)建完成后監(jiān)控aggregated事件和觸發(fā)this.watcher.watch(files.concat(missing), dirs.concat(missing), startTime)方法,并且關(guān)閉舊的Watchpack實例;在watch中會調(diào)用WatcherManager為每一個文件所在目錄創(chuàng)建的文件夾創(chuàng)建一個DirectoryWatcher對象,在DirectoryWatcher對象的watch構(gòu)造函數(shù)中調(diào)用chokidar插件進行文件夾監(jiān)聽,并且綁定一堆觸發(fā)事件并返回watcher;Watchpack會給每一個watcher注冊一個監(jiān)聽change事件,每當有文件變化時會觸發(fā)change事件。
在Watchpack插件監(jiān)聽的文件變化后設(shè)置一個定時器去延遲觸發(fā)change事件,解決多次快速修改時頻繁觸發(fā)問題。
當文件變化時NodeWatchFileStstem中的aggregated監(jiān)聽事件根據(jù)watcher獲取每一個監(jiān)聽文件的最后修改時間,并把該對象存放在this.compiler.fileTimestamps上然后觸發(fā) _go方法去構(gòu)建。
在compile中會把this.fileTimestamps賦值給compilation對象,在make階段從入口開始,遞歸構(gòu)建所有module,和首次構(gòu)建不同的是在compilation.addModule方法會首先去緩存中根據(jù)資源路徑取出module,然后拿module.buildTimestamp(module最后修改時間)和fileTimestamps中的該文件最后修改時間進行比較,若文件修改時間大于buildTimestamp則重新bulid該module,否則遞歸查找該module的的依賴。
在webpack構(gòu)建過程中是文件解析和模塊構(gòu)建比較耗時,所以webpack在build過程中已經(jīng)把文件絕對路徑和module已經(jīng)緩存起來,在rebuild時只會操作變化的module,這樣可以大大提升webpack的rebuild過程。
https://github.com/lihongxun9...
當完成編譯的時候,就通過 websocket 發(fā)送給客戶端一個消息(一個 hash 和 一個ok)
向client發(fā)送一條更新消息 當有文件發(fā)生變動的時候,webpack編譯文件,并通過 websocket 向client發(fā)送一條更新消息
//webpack-dev-server/lib/Server.js compiler.plugin("done", (stats) => { // 當完成編譯的時候,就通過 websocket 發(fā)送給客戶端一個消息(一個 `hash` 和 一個`ok`) this._sendStats(this.sockets, stats.toJson(clientStats)); });回顧webpack整體詳細流程
webpack主要是使用Compiler和Compilation類來控制webpack的整個生命周期,定義執(zhí)行流程;他們都繼承了tabpable并且通過tabpable來注冊了生命周期中的每一個流程需要觸發(fā)的事件。
webpack內(nèi)部實現(xiàn)了一堆plugin,這些內(nèi)部plugin是webpack打包構(gòu)建過程中的功能實現(xiàn),訂閱感興趣的事件,在執(zhí)行流程中調(diào)用不同的訂閱函數(shù)就構(gòu)成了webpack的完整生命周期。
其中:[event-name]代表 事件名
[---初始化階段---]
初始化參數(shù):webpack.config.js / shell+yargs(optimist) 獲取配置options
初始化 Compiler 實例 (全局只有一個,繼承自Tapable,大多數(shù)面向用戶的插件,都是首先在 Compiler 上注冊的)
Compiler:存放輸入輸出配置+編譯器Parser對象
Watching():監(jiān)聽文件變化
初始化 complier上下文,loader和file的輸入輸出環(huán)境
初始化礎(chǔ)插件WebpacOptionsApply()(根據(jù)options)
[entry-option] :讀取配置的 Entrys,為每個 Entry 實例化一個對應的 EntryPlugin,為后面該 Entry 的遞歸解析工作做準備
[after-plugins] : 調(diào)用完所有內(nèi)置的和配置的插件的 apply 方法。
[after-resolvers] : 根據(jù)配置初始化完 resolver,resolver 負責在文件系統(tǒng)中尋找指定路徑的文件。
[environment] : 開始應用 Node.js 風格的文件系統(tǒng)到 compiler 對象,以方便后續(xù)的文件尋找和讀取。
[after-environment]
[----構(gòu)建Graph階段 1----]
入口文件出發(fā),調(diào)用所有配置的 Loader 對模塊進行翻譯,再找出該模塊依賴的模塊,再遞歸本步驟直到所有入口依賴的文件都經(jīng)過了本步驟的處理
[before-run]
[run]啟動一次新的編譯
- 使用信息`Compiler.readRecords(cb)` - 觸發(fā)`Compiler.compile(onCompiled)` (開始構(gòu)建options中模塊) - 創(chuàng)建參數(shù)`Compiler.newCompilationParams()`
[normal-module-factory] 引入NormalModule工廠函數(shù)
[context-module-factory] 引入ContextModule工廠函數(shù)
[before-compile]執(zhí)行一些編譯之前需要處理的插件
[compile]
- 實例化`compilation`對象 - `Compiler.newCompilation(params)` - `Compiler.createCompilation()`
該對象負責組織整個編譯過程,包含了每個構(gòu)建環(huán)節(jié)對應的方法。對象內(nèi)部保留了對`compile`的引用,供plugin使用,并存放所有modules,chunks,assets(對應entry),template。根據(jù)test正則找到導入,并分配唯一id
[this-compilation]觸發(fā) compilation 事件之前
[compilation]通知訂閱的插件,比如在compilation.dependencyFactories中添加依賴工廠類等操作
[----構(gòu)建Graph階段 2----]
[make]是compilation初始化完成觸發(fā)的事件
通知在WebpackOptionsApply中注冊的EntryOptionPlugin插件
EntryOptionPlugin插件使用entries參數(shù)創(chuàng)建一個單入口(SingleEntryDependency)或者多入口(MultiEntryDependency)依賴,多個入口時在make事件上注冊多個相同的監(jiān)聽,并行執(zhí)行多個入口
tapAsync注冊了一個DllEntryPlugin, 就是將入口模塊通過調(diào)用compilation.addEntry()方法將所有的入口模塊添加到編譯構(gòu)建隊列中,開啟編譯流程。
隨后在addEntry 中調(diào)用_addModuleChain開始編譯。在_addModuleChain首先會生成模塊,最后構(gòu)建。在_addModuleChain中根據(jù)依賴查找對應的工廠函數(shù),并調(diào)用工廠函數(shù)的create來生成一個空的MultModule對象,并且把MultModule對象存入compilation的modules中后執(zhí)行MultModule.build,因為是入口module,所以在build中沒處理任何事直接調(diào)用了afterBuild;在afterBuild中判斷是否有依賴,若是葉子結(jié)點直接結(jié)束,否則調(diào)用processModuleDependencies方法來查找依賴
上面講述的afterBuild肯定至少存在一個依賴,processModuleDependencies方法就會被調(diào)用;processModuleDependencies根據(jù)當前的module.dependencies對象查找該module依賴中所有需要加載的資源和對應的工廠類,并把module和需要加載資源的依賴作為參數(shù)傳給addModuleDependencies方法;在addModuleDependencies中異步執(zhí)行所有的資源依賴,在異步中調(diào)用依賴的工廠類的create去查找該資源的絕對路徑和該資源所依賴所有l(wèi)oader的絕對路徑,并且創(chuàng)建對應的module后返回;然后根據(jù)該module的資源路徑作為key判斷該資源是否被加載過,若加載過直接把該資源引用指向加載過的module返回;否則調(diào)用this.buildModule方法執(zhí)行module.build加載資源;build完成就得到了loader處理過后的最終module了,然后遞歸調(diào)用afterBuild,直到所有的模塊都加載完成后make階段才結(jié)束。
在make階段webpack會根據(jù)模塊工廠(normalModuleFactory)的create去實例化module;實例化moduel后觸發(fā)this.hooks.module事件,若構(gòu)建配置中注冊了DllReferencePlugin插件,DelegatedModuleFactoryPlugin會監(jiān)聽this.hooks.module事件,在該插件里判斷該moduel的路徑是否在this.options.content中,若存在則創(chuàng)建代理module(DelegatedModule)去覆蓋默認module;DelegatedModule對象的delegateData中存放manifest中對應的數(shù)據(jù)(文件路徑和id),所以DelegatedModule對象不會執(zhí)行bulled,在生成源碼時只需要在使用的地方引入對應的id即可。
make結(jié)束后會把所有的編譯完成的module存放在compilation的modules數(shù)組中,通過單例模式保證同樣的模塊只有一個實例,modules中的所有的module會構(gòu)成一個圖。
[before-resolve]準備創(chuàng)建Module
[factory]根據(jù)配置創(chuàng)建Module的工廠類Factory(Compiler.js) 使用Factory創(chuàng)建 NormalModule實例 根據(jù)rule.modules創(chuàng)建RulesSet規(guī)則集
[resolver]通過loader的resolver來解析loader路徑
[resolve]使用loaderResolver解析loader模塊路徑
[resolve-step]
[file]
[directory]
[resolve-step]
[result]
[after-resolve]
[create-module]
[module]
[build-module] NormalModule實例.build() 進行模塊的構(gòu)建
[normal-build-loader] acron對DSL進行AST分析
[program] 遇到require創(chuàng)建依賴收集;異步處理依賴的module,循環(huán)處理依賴的依賴
[statement]
[succeed-module]
[---- 優(yōu)化Graph----]
compilation.seal(cb)根據(jù)之前收集的依賴,決定生成多少文件,每個文件的內(nèi)容是什么. 對每個module和chunk整理,生成編譯后的源碼,合并,拆分,生成 hash,保存在compilation.assets,compilation.chunk
[seal]密封已經(jīng)開始。不再接受任何Module
[optimize] 優(yōu)化編譯. 觸發(fā)optimizeDependencies類型的一些事件去優(yōu)化依賴(比如tree shaking就是在這個地方執(zhí)行的)
根據(jù)入口module創(chuàng)建chunk,如果是單入口就只有一個chunk,多入口就有多個chunk;
根據(jù)chunk遞歸分析查找module中存在的異步導module,并以該module為節(jié)點創(chuàng)建一個chunk,和入口創(chuàng)建的chunk區(qū)別在于后面調(diào)用模版不一樣。
所有chunk執(zhí)行完后會觸發(fā)optimizeModules和optimizeChunks等優(yōu)化事件通知感興趣的插件進行優(yōu)化處理。
createChunkAssets生產(chǎn)assets給chunk生成hash然后調(diào)用createChunkAssets來根據(jù)模版生成源碼對象.所有的module,chunk任然保存的是通過一個個require聚合起來的代碼,需要通過template產(chǎn)生最后帶有__webpack__reuqire()的格式。
createChunkAssets.jpg
根據(jù)chunks生產(chǎn)sourceMap使用summarizeDependencies把所有解析的文件緩存起來,最后調(diào)用插件生成soureMap和最終的數(shù)據(jù)
把assets中的對象生產(chǎn)要輸出的代碼assets是一個對象,以最終輸出名稱為key存放的輸出對象,每一個輸出文件對應著一個輸出對象
[after-optimize-assets]資產(chǎn)已經(jīng)優(yōu)化
[after-compile] 一次 Compilation 執(zhí)行完成。
[---- 渲染Graph----]
[should-emit] 所有需要輸出的文件已經(jīng)生成好,詢問插件哪些文件需要輸出,哪些不需要。
Compiler.emitAssets()
[emit]
按照 output 中的配置項異步將將最終的文件輸出到了對應的 path 中
output:plugin結(jié)束前,在內(nèi)存中生成一個compilation對象文件模塊tree,枝葉節(jié)點就是所有的module(由import或者require為標志,并配備唯一moduleId),主枝干就是所有的assets,也就是我們最后需要寫入到output.path文件夾里的文件內(nèi)容。
MainTemplate.render()和ChunkTemplate.render()處理入口文件的module 和 非首屏需異步加載的module
MainTemplate.render()
處理不同的模塊規(guī)范Commonjs,AMD...
生成好的js保存在compilation.assets中
[asset-path]
[after-emit]
[done]
if needAdditionalPass
needAdditionalPass()
回到compiler.run
else this.emitRecords(cb)
調(diào)用戶自定義callback
[failed] 如果在編譯和輸出流程中遇到異常導致 Webpack 退出時,就會直接跳轉(zhuǎn)到本步驟,插件可以在本事件中獲取到具體的錯誤原因。
參考資料webpack loader 機制源碼解析
【webpack進階】你真的掌握了loader么?- loader十問
webpack源碼解析
webpack tapable 原理詳解
webpack4源碼分析
隨筆分類 - webpack源碼系列
webpack the confusing parts
細說 webpack 之流程篇
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/103877.html
摘要:所有注冊的處理方法并行執(zhí)行,相互獨立互不干擾。倘若某一個處理函數(shù)報錯,則執(zhí)行傳入的,后續(xù)的處理函數(shù)將不被執(zhí)行,否則最后一個處理函數(shù)調(diào)用。順序由注冊插件順序決定,而不是由函數(shù)的執(zhí)行時間。如果為此名稱注冊了插件,則返回。 Tapable https://github.com/webpack/ta...https://segmentfault.com/a/11... var Tapable ...
摘要:前端安全的分類基本概念和縮寫跨站請求偽造,英文名,縮寫。攻擊原理不可缺少的兩大因素用戶一定在注冊網(wǎng)站登陸過網(wǎng)站的某一接口有漏洞引誘鏈接會自動攜帶,不會自動攜帶。 1.前端安全的分類 CSRF XSS 2.CSRF 基本概念和縮寫:跨站請求偽造,英文名Cross-site request forgery,縮寫CSRF。攻擊原理:showImg(https://segmentfault...
摘要:原型方法通過原型方法方法來掛載實例。當響應式屬性發(fā)生變化時,會通知依賴列表中的對象進行更新。此時,對象執(zhí)行方法,重新渲染節(jié)點。在執(zhí)行過程中,如果需要讀取響應式屬性,則會觸發(fā)響應式屬性的??偨Y(jié)響應式屬性的原理 vue實例 初始化 完成以后,接下來就要進行 掛載。 vue實例掛載,即為將vue實例對應的 template模板,渲染成 Dom節(jié)點。 原型方法 - $mount ? 通過原...
閱讀 2006·2021-11-24 10:45
閱讀 1861·2021-10-09 09:43
閱讀 1303·2021-09-22 15:38
閱讀 1230·2021-08-18 10:19
閱讀 2849·2019-08-30 15:55
閱讀 3069·2019-08-30 12:45
閱讀 2975·2019-08-30 11:25
閱讀 365·2019-08-29 11:30