自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

淺談 Webpack 背后的運(yùn)行機(jī)制

開發(fā) 開發(fā)工具
在平時開發(fā)中我們經(jīng)常會用到 Webpack這個時下最流行的前端打包工具。它打包開發(fā)代碼,輸出能在各種瀏覽器運(yùn)行的代碼,提升了開發(fā)至發(fā)布過程的效率。

 在平時開發(fā)中我們經(jīng)常會用到 Webpack這個時下流行的前端打包工具。它打包開發(fā)代碼,輸出能在各種瀏覽器運(yùn)行的代碼,提升了開發(fā)至發(fā)布過程的效率。

我們知道一份 Webpack配置文件主要包含入口( entry)、輸出文件( output)、模式、加載器( Loader)、插件( Plugin)等幾個部分。但如果只需要組織 JS 文件的話,指定入口和輸出文件路徑即可完成一個迷你項目的打包。下面我們來通過一個簡單的項目來看一下 Webpack是怎樣運(yùn)行的。

[[273840]]

同步加載

本文使用 webpack ^4.30.0 作示例.為了更好地觀察產(chǎn)出的文件,我們將模式設(shè)置為 development 關(guān)閉代碼壓縮,再開啟 source-map 支持原始源代碼調(diào)試。除此之外。我們還簡單的寫了一個插件 MyPlugin來去除源碼中的注釋。

新建 src/index.js

  1. console.log( Hello webpack! ); 

新建 webpack配置文件 webpack.config.js

  1. const path = require( path ); 
  2. const MyPlugin = require( ./src/MyPlugin.js ) 
  3.  
  4. module.exports = { 
  5. mode: development , 
  6. devtool: source-map , 
  7. entry: ./src/index.js , 
  8. output: { 
  9. path: path.resolve(__dirname, dist ) 
  10. }, 
  11. plugins:[ 
  12. new MyPlugin() 
  13. }; 

新建 src/MyPlugin.js。

  1. class MyPlugin { 
  2. constructor(options) { 
  3. this.options = options 
  4. this.externalModules = {} 
  5. apply(compiler) { 
  6. var reg = /("([^\"]*(\.)?)*")|( ([^\ ]*(\.)?)* )|(/{2,}.*?( 
  7. ))|(/*( 
  8. |.)*?*/)|(/******/)/g 
  9. compiler.hooks.emit.tap( CodeBeautify , (compilation)=> { 
  10. Object.keys(compilation.assets).forEach((data)=> { 
  11. let content = compilation.assets[data].source() // 欲處理的文本 
  12. content = content.replace(reg, function (word) { // 去除注釋后的文本 
  13. return /^/{2,}/.test(word) || /^/*!/.test(word) || /^/*{3,}//.test(word) ? "" : word; 
  14. }); 
  15. compilation.assets[data] = { 
  16. source(){ 
  17. return content 
  18. }, 
  19. size(){ 
  20. return content.length 
  21. }) 
  22. }) 
  23. module.exports = MyPlugin 

現(xiàn)在我們運(yùn)行命令 webpack --config webpack.config.js ,打包完成后會多出一個輸出目錄 dist: dist/main.js。 main 是 webpack 默認(rèn)設(shè)置的輸出文件名,我們快速瞄一眼這個文件:

  1. (function(modules){ 
  2. // ... 
  3. })({ 
  4. "./src/index.js": (function(){ 
  5. // ... 
  6. }) 
  7. }); 

整個文件只含一個立即執(zhí)行函數(shù) (IIFE),我們稱它為 webpackBootstrap,它僅接收一個對象 —— 未加載的 模塊集合( modules),這個 modules 對象的 key 是一個路徑, value 是一個函數(shù)。你也許會問,這里的模塊是什么?它們又是如何加載的呢?

在細(xì)看產(chǎn)出代碼前,我們先豐富一下源代碼:

新文件 src/utils/math.js:

  1. export const plus = (a, b) => {  
  2. return a + b;  
  3. }; 

修改 src/index.js:

  1. import { plus } from ./utils/math.js ; 
  2. console.log( Hello webpack! ); 
  3.  
  4. console.log( 1 + 2: , plus(1, 2)); 

我們按照 ES 規(guī)范的模塊化語法寫了一個簡單的模塊 src/utils/math.js,給 src/index.js 引用。Webpack 用自己的方式支持了 ES6 Module 規(guī)范,前面提到的 module 就是和 ES6 module 對應(yīng)的概念。

接下來我們看一下這些模塊是如何通 ES5 代碼實(shí)現(xiàn)的。再次運(yùn)行命令 webpack --config webpack.config.js后查看輸出文件:

  1. (function(modules){ 
  2. // ... 
  3. })({ 
  4. "./src/index.js": (function(){ 
  5. // ... 
  6. }), 
  7. "./src/utils/math.js": (function() { 
  8. // ... 
  9. }) 
  10. }); 

IIFE 傳入的 modules 對象里多了一個鍵值對,對應(yīng)著新模塊 src/utils/math.js,這和我們在源代碼中拆分的模塊互相呼應(yīng)。然而,有了 modules 只是第一步,這份文件最終達(dá)到的效果應(yīng)該是讓各個模塊按開發(fā)者編排的順序運(yùn)行。

探究 webpackBootstrap

接下來看看 webpackBootstrap 函數(shù)中有些什么:

  1. // webpackBootstrap 
  2. (function(modules){ 
  3. // 緩存 __webpack_require__ 函數(shù)加載過的模塊 
  4. var installedModules = {}; 
  5. /** 
  6. * Webpack 加載函數(shù),用來加載 webpack 定義的模塊 
  7. * @param {String} moduleId 模塊 ID,一般為模塊的源碼路徑,如 "./src/index.js" 
  8. * @returns {Object} exports 導(dǎo)出對象 
  9. */ 
  10. function __webpack_require__(moduleId) { 
  11. // ... 
  12. // 在 __webpack_require__ 函數(shù)對象上掛載一些變量及函數(shù) ... 
  13. // 傳入表達(dá)式的值為 "./src/index.js" 
  14. return __webpack_require__(__webpack_require__.s = "./src/index.js"); 
  15. })(/* modules */); 

可以看到其實(shí)主要做了兩件事:

  1. 定義一個模塊加載函數(shù) __webpack_require__。
  2. 使用加載函數(shù)加載入口模塊 "./src/index.js"。

整個 webpackBootstrap 中只出現(xiàn)了入口模塊的影子,那其他模塊又是如何加載的呢?我們順著 __webpack_require__("./src/index.js") 細(xì)看加載函數(shù)的內(nèi)部邏輯:

  1. function __webpack_require__(moduleId) { 
  2. // 重復(fù)加載則利用緩存 
  3. if (installedModules[moduleId]) { 
  4. return installedModules[moduleId].exports; 
  5. // 如果是第一次加載,則初始化模塊對象,并緩存 
  6. var module = installedModules[moduleId] = { 
  7. i: moduleId, // 模塊 ID 
  8. l: false, // 模塊加載標(biāo)識 
  9. exports: {} // 模塊導(dǎo)出對象 
  10. }; 
  11. /** 
  12. * 執(zhí)行模塊 
  13. * @param module.exports -- 模塊導(dǎo)出對象引用,改變模塊包裹函數(shù)內(nèi)部的 this 指向 
  14. * @param module -- 當(dāng)前模塊對象引用 
  15. * @param module.exports -- 模塊導(dǎo)出對象引用 
  16. * @param __webpack_require__ -- 用于在模塊中加載其他模塊 
  17. */ 
  18. modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 
  19. // 模塊加載標(biāo)識置為已加載 
  20. module.l = true
  21. // 返回當(dāng)前模塊的導(dǎo)出對象引用 
  22. return module.exports; 

首先,加載函數(shù)使用了閉包變量 installedModules,用來將已加載過的模塊保存在內(nèi)存中。接著是初始化模塊對象,并把它掛載到緩存里。然后是模塊的執(zhí)行過程,加載入口文件時 modules[moduleId] 其實(shí)就是 ./src/index.js 對應(yīng)的模塊函數(shù)。執(zhí)行模塊函數(shù)前傳入了跟模塊相關(guān)的幾個實(shí)參,讓模塊可以導(dǎo)出內(nèi)容,以及加載其他模塊的導(dǎo)出。最后標(biāo)識該模塊加載完成,返回模塊的導(dǎo)出內(nèi)容。

根據(jù) __webpack_require__ 的緩存和導(dǎo)出邏輯,我們得知在整個 IIFE 運(yùn)行過程中,加載已緩存的模塊時,都會直接返回 installedModules[moduleId].exports,換句話說,相同的模塊只有在第一次引用的時候才會執(zhí)行模塊本身。

模塊執(zhí)行函數(shù)

__webpack_require__ 中通過 modules[moduleId].call() 運(yùn)行了模塊執(zhí)行函數(shù),下面我們就進(jìn)入到 webpackBootstrap 的參數(shù)部分,看看模塊的執(zhí)行函數(shù)。

  1. /*** 入口模塊 ./src/index.js ***/ 
  2. "./src/index.js": (function (module, __webpack_exports__, __webpack_require__) { 
  3. "use strict"
  4. // 用于區(qū)分 ES 模塊和其他模塊規(guī)范,不影響理解 demo,戰(zhàn)略跳過。 
  5. __webpack_require__.r(__webpack_exports__); 
  6. /* harmony import */ 
  7. // 源模塊代碼中,`import {plus} from ./utils/math.js ;` 語句被 loader 解析轉(zhuǎn)化。 
  8. // 加載 "./src/utils/math.js" 模塊, 
  9. var _utils_math_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/utils/math.js"); 
  10. console.log( Hello webpack! ); 
  11. console.log( 1 + 2: , Object(_utils_math_js__WEBPACK_IMPORTED_MODULE_0__["plus"])(1, 2)); 
  12. }), 
  13.  
  14.  
  15. "./src/utils/math.js": (function (module, __webpack_exports__, __webpack_require__) { 
  16. "use strict"
  17. __webpack_require__.r(__webpack_exports__); 
  18. /* harmony export (binding) */ 
  19. // 源模塊代碼中,`export` 語句被 loader 解析轉(zhuǎn)化。 
  20. __webpack_require__.d(__webpack_exports__, "plus"function () { 
  21. return plus; 
  22. }); 
  23. const plus = (a, b) => { 
  24. return a + b; 
  25. }; 
  26. }) 

執(zhí)行順序是:入口模塊 -> 工具模塊 -> 入口模塊。入口模塊中首先就通過 __webpack_require__("./src/utils/math.js") 拿到了工具模塊的 exports 對象。再看工具模塊, ES 導(dǎo)出語法轉(zhuǎn)化成了 __webpack_require__.d(__webpack_exports__, [key], [getter]),而 __webpack_require__.d 函數(shù)的定義在 webpackBootstrap 內(nèi):

  1. // 定義 exports 對象導(dǎo)出的屬性。 
  2. __webpack_require__.d = function (exports, name, getter) { 
  3. // 如果 exports (不含原型鏈上)沒有 [name] 屬性,定義該屬性的 getter。 
  4. if (!__webpack_require__.o(exports, name)) { 
  5. Object.defineProperty(exports, name, { 
  6. enumerable: true
  7. get: getter 
  8. }); 
  9. }; 
  10. // 包裝 Object.prototype.hasOwnProperty 函數(shù)。 
  11. __webpack_require__.o = function (object, property) { 
  12. return Object.prototype.hasOwnProperty.call(object, property); 
  13. }; 

可見 __webpack_require__.d 其實(shí)就是 Object.defineProperty 的簡單包裝。

引用工具模塊導(dǎo)出的變量后,入口模塊再執(zhí)行它剩余的部分。至此,Webpack 基本的模塊執(zhí)行過程就結(jié)束了。

好了,我們用流程圖總結(jié)一下 Webpack 模塊的加載思路:

 

異步加載

有上面的打包我們發(fā)現(xiàn)將不同的打包進(jìn)一個 main.js 文件。 main.js 會集中消耗太多網(wǎng)絡(luò)資源,導(dǎo)致用戶需要等待很久才可以開始與網(wǎng)頁交互。

一般的解決方式是:根據(jù)需求降低首次加載文件的體積,在需要時(如切換前端路由器,交互事件回調(diào))異步加載其他文件并使用其中的模塊。

Webpack 推薦用 ES import() 規(guī)范來異步加載模塊,我們根據(jù) ES 規(guī)范修改一下入口模塊的 import 方式,讓其能夠異步加載模塊:

  1. src/index.js 
  2.  
  3. console.log( Hello webpack! );window.setTimeout(() => {  import( ./utils/math ).then(mathUtil => {  console.log( 1 + 2:   + mathUtil.plus(1, 2));  });}, 2000); 

工具模塊 (src/utils/math.js)依然不變,在 webpack 配置里,我們指定一下資源文件的公共資源路徑 (publicPath),后面的探索過程中會遇到。

  1. const path = require( path ); 
  2. const MyPlugin = require( ./src/MyPlugin.js ) 
  3.  
  4.  
  5. module.exports = { 
  6. mode: development , 
  7. devtool: source-map , 
  8. entry: ./src/index.js , 
  9. output: { 
  10. path: path.resolve(__dirname, dist ), 
  11. publicPath: /dist/ 
  12. }, 
  13. plugins:[ 
  14. new MyPlugin() 
  15. }; 

接著執(zhí)行一下打包,可以看到除了 dist/main.js 外,又多了一個 dist/0.js ./src/utils/math.js。模塊從 main chunk 遷移到了 0 chunk 中。而與 demo1 不同的是, main chunk 中添加了一些用于異步加載的代碼,我們概覽一下:

  1. // webpackBootstrap 
  2. (function (modules) { 
  3. // 加載其他 chunk 后的回調(diào)函數(shù) 
  4. function webpackJsonpCallback(data) { 
  5. // ... 
  6. // ... 
  7. // 用于緩存 chunk 的加載狀態(tài),0 為已加載 
  8. var installedChunks = { 
  9. "main": 0 
  10. }; 
  11. // 拼接 chunk 的請求地址 
  12. function jsonpScriptSrc(chunkId) { 
  13. // ... 
  14. // 同步 require 函數(shù),內(nèi)容不變 
  15. function __webpack_require__(moduleId) { 
  16. // ... 
  17. // 異步加載 chunk,返回封裝加載過程的 promise 
  18. __webpack_require__.e = function requireEnsure(chunkId) { 
  19. // ... 
  20. // ... 
  21. // defineProperty 的包裝,內(nèi)容不變 
  22. __webpack_require__.d = function (exports, name, getter) {} 
  23. // ... 
  24. // 根據(jù)配置文件確定的 publicPath 
  25. __webpack_require__.p = "/dist/"
  26. /**** JSONP 初始化 ****/ 
  27. var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; 
  28. var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); 
  29. jsonpArray.push = webpackJsonpCallback; 
  30. jsonpArray = jsonpArray.slice(); 
  31. for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); 
  32. var parentJsonpFunction = oldJsonpFunction; 
  33. /**** JSONP 初始化 ****/ 
  34. return __webpack_require__(__webpack_require__.s = "./src/index.js"); 
  35. })({ 
  36. "./src/index.js": (function(module, exports, __webpack_require__) { 
  37. document.write( Hello webpack! ); 
  38. window.setTimeout(() => { 
  39. __webpack_require__.e(/*! import() */ 0).then(__webpack_require__.bind(null, /*! ./utils/math */ "./src/utils/math.js")).then(mathUtil => { 
  40. console.log( 1 + 2: + mathUtil.plus(1, 2)); 
  41. }); 
  42. }, 2000); 
  43. }) 
  44. }) 

可以看到 webpackBootstrap 的函數(shù)體部分增加了一些內(nèi)容,參數(shù)部分移除了 "./src/utils/math.js" 模塊。跟著包裹函數(shù)的執(zhí)行順序,我們先聚焦到 「JSONP 初始化」部分:

  1. // 存儲 jsonp 的數(shù)組,首次運(yùn)行為 [] 
  2. var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; 
  3. // 保存 jsonpArray 的 push 函數(shù),首次運(yùn)行為 Array.prototype.push 
  4. var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); 
  5. // 將 jsonpArray 的 push 重寫為 webpackJsonpCallback (加載其他 chunk 后的回調(diào)函數(shù)) 
  6. jsonpArray.push = webpackJsonpCallback; 
  7. // 將 jsonpArray 重置為正常數(shù)組,push 重置為 Array.prototype.push 
  8. jsonpArray = jsonpArray.slice(); 
  9. // 由于 jsonpArray 為 [],不做任何事 
  10. for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); 
  11. // Array.prototype.push 
  12. var parentJsonpFunction = oldJsonpFunction; 

初始化結(jié)束后,變化就是 window 上掛載了一個 webpackJsonp 數(shù)組,它的值為 [];此外,這個數(shù)組的 push 被改寫為 webpackJsonpCallback 函數(shù),我們在后面會提到這些準(zhǔn)備工作的作用。

接著是 __webpack_require__ 入口模塊,由于 __webpack_require__ 函數(shù)沒有改變,我們繼續(xù)觀察入口模塊執(zhí)行函數(shù)有了什么變化。

顯然, import( ../utils/math.js ) 被轉(zhuǎn)化為 __webpack_require__.e(0).then(__webpack_require__.bind(null,"./src/utils/math.js"))。0 是 ./src/utils/math.js 所在 chunk 的 id,「同步加載模塊」的邏輯拆分成了「先加載 chunk,完成后再加載模塊」。

我們翻到 __webpack_require__.e 的定義位置:

  1. __webpack_require__.e = function requireEnsure(chunkId) { 
  2. var promises = []; 
  3. // installedChunks 是在 webpackBootstrap 中維護(hù)的 chunk 緩存 
  4. var installedChunkData = installedChunks[chunkId]; 
  5. // chunk 未加載 
  6. if(installedChunkData !== 0) { 
  7. // installedChunkData 為 promise 表示 chunk 加載中 
  8. if(installedChunkData) { 
  9. promises.push(installedChunkData[2]); 
  10. else { 
  11. /*** 首次加載 chunk: ***/ 
  12. // 初始化 promise 對象 
  13. var promise = new Promise(function(resolve, reject) { 
  14. installedChunkData = installedChunks[chunkId] = [resolve, reject]; 
  15. }); 
  16. promises.push(installedChunkData[2] = promise); 
  17. // 創(chuàng)建 script 標(biāo)簽加載 chunk 
  18. var head = document.getElementsByTagName( head )[0]; 
  19. var script = document.createElement( script ); 
  20. var onScriptComplete; 
  21. // ... 省略一些 script 屬性設(shè)置 
  22. // src 根據(jù) publicPath 和 chunkId 拼接 
  23. script.src = jsonpScriptSrc(chunkId); 
  24. // 加載結(jié)束回調(diào)函數(shù),處理 script 加載完成、加載超時、加載失敗的情況 
  25. onScriptComplete = function (event) { 
  26. script.onerror = script.onload = null; // 避免 IE 內(nèi)存泄漏問題 
  27. clearTimeout(timeout); 
  28. var chunk = installedChunks[chunkId]; 
  29. // 處理 script 加載完成,但 chunk 沒有加載完成的情況 
  30. if(chunk !== 0) { 
  31. // chunk 加載中 
  32. if(chunk) { 
  33. var errorType = event && (event.type === load ? missing : event.type); 
  34. var realSrc = event && event.target && event.target.src; 
  35. var error = new Error( Loading chunk + chunkId + failed. 
  36. ( + errorType + : + realSrc + ) ); 
  37. error.type = errorType; 
  38. error.request = realSrc; 
  39. // reject(error) 
  40. chunk[1](error); 
  41. // 統(tǒng)一將沒有加載的 chunk 標(biāo)記為未加載 
  42. installedChunks[chunkId] = undefined; 
  43. }; 
  44. // 設(shè)置 12 秒超時時間 
  45. var timeout = setTimeout(function(){ 
  46. onScriptComplete({ type: timeout , target: script }); 
  47. }, 120000); 
  48. script.onerror = script.onload = onScriptComplete; 
  49. head.appendChild(script); 
  50. /*** 首次加載 chunk ***/ 
  51. return Promise.all(promises); 
  52. }; 

看起來有點(diǎn)長,我們一步步剖析,先從第一行和最后一行來看,整個函數(shù)將異步加載的過程封裝到了 promise 中,最終導(dǎo)出。

接著從第二行開始, installedChunkData 從緩存中取值,顯然首次加載 chunk 時此處是 undefined。接下來, installedChunkData 的 undefined 值觸發(fā)了第一層 if 語句的判斷條件。緊接著進(jìn)行到第二層 if 語句,此時根據(jù)判斷條件走入 else 塊,這里 if 塊里的內(nèi)容我們先戰(zhàn)略跳過,else 里主要有兩塊內(nèi)容,一是 chunk 腳本加載過程,這個過程創(chuàng)建了一個 script 標(biāo)簽,使其請求 chunk所在地址并執(zhí)行 chunk 內(nèi)容;二是初始化 promise ,并用 promise 控制 chunk 文件加載過程。

不過,我們只在這段 else 代碼塊中找到了 reject 的使用處,也就是在 chunk 加載異常時 chunk[1](error) 的地方,但并沒發(fā)現(xiàn)更重要的 resolve 的使用地點(diǎn),僅僅是把 resolve 掛在了緩存上 (installedChunks[chunkId] = [resolve, reject])。

這里的 chunk 文件加載下來會發(fā)生什么呢?讓我們打開 dist/0.js 一探究竟:

  1. (window["webpackJsonp"] = window["webpackJsonp"] || []).push([[0], { 
  2. "./src/utils/math.js"
  3. (function (module, __webpack_exports__, __webpack_require__) { 
  4.  
  5.  
  6. "use strict"
  7. __webpack_require__.r(__webpack_exports__); 
  8. /* harmony export (binding) */ 
  9. __webpack_require__.d(__webpack_exports__, "plus"function () { 
  10. return plus; 
  11. }); 
  12. const plus = (a, b) => { 
  13. return a + b; 
  14. }; 
  15. }) 
  16. }]); 

我們發(fā)現(xiàn)了:

  1. 久違的 ./src/utils/math.js 模塊
  2. window["webpackJsonp"] 數(shù)組的使用地點(diǎn)

這段代碼開始執(zhí)行,把異步加載相關(guān)的 chunk id 與模塊傳給 push 函數(shù)。而前面已經(jīng)提到過, window["webpackJsonp"] 數(shù)組的 push 函數(shù)已被重寫為 webpackJsonpCallback 函數(shù),它的定義位置在 webpackBootstrap 中:

  1. function webpackJsonpCallback(data) { 
  2. var chunkIds = data[0]; 
  3. var moreModules = data[1]; 
  4. // then flag all "chunkIds" as loaded and fire callback 
  5. var moduleId, chunkId, i = 0, resolves = []; 
  6. // 將 chunk 標(biāo)記為已加載 
  7. for(;i < chunkIds.length; i++) { 
  8. chunkId = chunkIds[i]; 
  9. if(installedChunks[chunkId]) { 
  10. resolves.push(installedChunks[chunkId][0]); 
  11. installedChunks[chunkId] = 0; 
  12. // 把 "moreModules" 加到 webpackBootstrap 中的 modules 閉包變量中。 
  13. for(moduleId in moreModules) { 
  14. if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { 
  15. modules[moduleId] = moreModules[moduleId]; 
  16. // parentJsonpFunction 是 window["webpackJsonp"] 的原生 push 
  17. // 將 data 加入全局?jǐn)?shù)組,緩存 chunk 內(nèi)容 
  18. if(parentJsonpFunction) parentJsonpFunction(data); 
  19. // 執(zhí)行 resolve 后,加載 chunk 的 promise 狀態(tài)變?yōu)?nbsp;resolved,then 內(nèi)的函數(shù)開始執(zhí)行。 
  20. while(resolves.length) { 
  21. resolves.shift()(); 
  22. }; 

走進(jìn)這個函數(shù)中,意味著異步加載的 chunk 內(nèi)容已經(jīng)拿到,這個時候我們要完成兩件事,一是讓依賴這次異步加載結(jié)果的模塊繼續(xù)執(zhí)行,二是緩存加載結(jié)果。

關(guān)于第一點(diǎn),我們回憶一下之前 __webpack_require__.e 的內(nèi)容,此時 chunk 還處于「加載中」的狀態(tài),也就是說對應(yīng)的 installedChunks[chunkId] 的值此時為 [resolve, reject, promise]。而這里,chunk 已經(jīng)加載,但 promise 還未決議,于是 webpackJsonpCallback 內(nèi)部定義了一個 resolves 變量用來收集 installedChunks 上的 resolve 并執(zhí)行它。

接下來說到第二點(diǎn),就要涉及幾個層面的緩存了。

首先是 chunk 層面,這里有兩個相關(guān)操作,操作一將 installedChunks[chunkId] 置為 0 可以讓 __webpack_require__.e 在第二次加載同一 chunk 時返回一個立即決議的 promise(Promise.all([]));操作二將 chunk data 添加進(jìn) window["webpackJsonp"] 數(shù)組,可以在多入口模式時,方便地拿到已加載過的 chunk 緩存。通過以下代碼實(shí)現(xiàn):

  1. /*** 緩存執(zhí)行部分 ***/ 
  2. var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; 
  3. // ... 
  4. for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); 
  5. var parentJsonpFunction = oldJsonpFunction; 
  6. /*** 緩存執(zhí)行部分 ***/ 
  7. /*** 緩存添加部分 ***/ 
  8. function webpackJsonpCallback(data) { 
  9. //... 
  10. // 此處的 parentJsonpFunction 是 window["webpackJsonp"] 數(shù)組的原生 push 
  11. if (parentJsonpFunction) parentJsonpFunction(data); 
  12. //... 
  13. /*** 緩存添加部分 ***/ 

而在 modules 層面, chunk 中的 moreModules 被合入入口文件的 modules 中,可供下一個微任務(wù)中的 __webpack_require__ 同步加載模塊。

  1. ({ 
  2. "./src/index.js"
  3. (function (module, exports, __webpack_require__) { 
  4. console.log( Hello webpack! ); 
  5. window.setTimeout(() => { 
  6. __webpack_require__.e(0).then(__webpack_require__.bind(null"./src/utils/math.js")).then(mathUtil => { 
  7. console.log( 1 + 2: + mathUtil.plus(1, 2)); 
  8. }); 
  9. }, 2000); 
  10. }) 
  11. }); 

__webpack_require__.e(0) 返回的 promise 決議后, __webpack_require__.bind(null,"./src/utils/math.js") 可以加載到 chunk 攜帶的模塊,并返回模塊作為下一個微任務(wù)函數(shù)的入?yún)?,接下來就?Webpack Loader 翻譯過的其他業(yè)務(wù)代碼了。

現(xiàn)在讓我們把異步流程梳理一下:

責(zé)任編輯:武曉燕 來源: Segmentfault
相關(guān)推薦

2019-05-10 14:00:21

小程序運(yùn)行機(jī)制前端

2018-12-26 16:30:09

SQL Server內(nèi)部運(yùn)行機(jī)制數(shù)據(jù)庫

2021-12-20 00:03:38

Webpack運(yùn)行機(jī)制

2015-11-20 11:20:54

js開發(fā)

2009-02-03 14:00:20

PHP運(yùn)行PHP調(diào)用PHP原理

2009-12-11 10:52:37

PHP運(yùn)行機(jī)制

2010-09-28 11:05:49

jQuery

2015-11-16 11:17:30

PHP底層運(yùn)行機(jī)制原理

2010-02-01 17:19:30

C++運(yùn)行機(jī)制

2010-05-06 17:54:54

Oracle鎖

2019-10-11 09:00:00

JavaScriptEvent Loop前端

2010-01-05 16:10:21

.NET Framew

2023-05-26 08:01:01

FacebookVelox機(jī)制

2012-03-06 10:22:00

程序

2010-02-23 10:15:22

WCF運(yùn)行機(jī)制

2009-10-22 17:10:04

CLR和JRE運(yùn)行機(jī)制

2016-12-13 14:12:25

程序機(jī)制

2016-12-14 14:41:20

Hello World程序運(yùn)行機(jī)制

2017-05-31 13:16:35

PHP運(yùn)行機(jī)制原理解析

2018-03-15 16:45:47

前端JavaScriptthis
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號