webpack联邦模块之webpack运行时
webpack是如何打包ES模塊的?webpack是如何構(gòu)建自身的模塊運(yùn)行時的?
__webpack_require__
這是整個webpack運(yùn)行時的核心。
該函數(shù)被用于根據(jù)模塊Id從變量__webpack_module_cache__獲取模塊對應(yīng)導(dǎo)出:
所以可以看出來__webpack_require__方法不用管模塊具體是怎么來的。該方法調(diào)用的時候,調(diào)用方需要確保該模塊id對應(yīng)的模塊已經(jīng)存在__webpack_modules__上了。
模塊id是什么?
模塊id是模塊文件名,也就是相對項目根目錄的完整路徑。
例如:import React from ‘react’,模塊react的id是./node_modules/react/index.js。
src/index.js下代碼 import Component from ‘./component/Abc’;
則模塊Component的id是 ./src/component/Abc/index.js
所以模塊id是唯一的
__webpack_require__.m = __webpack_modules__中的模塊是怎么來的?
首先看打包文件main.js最后有入口模塊的導(dǎo)入,
var __webpack_exports__ = __webpack_require__("./src/index.js");
這里的模塊 ./src/index.js 就是webpack配置文件中配置的入口模塊,而“./src/index.js” 則是模塊id,這個模塊被打包進(jìn)了main.js這個入口chunk里,直接掛在了 __webpack_modules__下。
來源1:到這里我們知道了入口模塊會直接掛在__webpack_modules__下
‘./src/index.js’這個模塊的源碼是import(’./bootstrap’)
所以這個入口模塊依賴模塊’./src/bootstrap.js’,并且這個是需要分割出去的異步模塊,所以不在入口chunk(main.js)的__webpack_modules__對象中。那么它是怎么被加載進(jìn)來的呢?
來源2:異步加載的分割模塊
這個流程很有意思,也很厲害。
打包后的入口模塊./src/index.js 內(nèi)容
__webpack_require__.e(/*! import() */ "src_bootstrap_js").then(__webpack_require__.bind(__webpack_require__, /*! ./bootstrap */ "./src/bootstrap.js"));其源碼很簡單就是import(’./bootstrap’),而編碼出來的內(nèi)容如上也很簡單。
可以看出來在__webpack_require__.e執(zhí)行完成之后就可以通過__webpack_require__去獲取’./src/bootstrap.js’ 模塊的內(nèi)容。
也就是說__wepack_require__.e和模塊’./src/bootstrap.js’的安裝有關(guān)系。
webpack_require.e的入?yún)⑹莄hunkId,模塊存在于chunk中
__webpack_require__.e 是整個webpack運(yùn)行時的基石
先看它的源碼
__webpack_require__.f = {}; // This file contains only the entry chunk. // The chunk loading function for additional chunks __webpack_require__.e = (chunkId) => {return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {__webpack_require__.f[key](chunkId, promises);return promises;}, [])); };這段代碼格式化后也才幾行,但是確實(shí)復(fù)雜。
這是一個設(shè)計模式,將依賴和實(shí)際依賴解耦。
__webpack_require__.f 上掛載了多個方法。其含義是,我要加載這個chunk了,你們要做什么嗎?
__webpack_require__.f上的方法remotes、consumes和j
其中remotes方法和consumes方法是聯(lián)邦模塊的核心,而方法j用于加載webpackchunk,也就是根據(jù)入?yún)hunkId加載對應(yīng)的chunk文件。
__webpack_require__.f.j 本質(zhì)上就是在加載chunkid對應(yīng)的JS文件
首先有一個chunk安裝情況索引:
// object to store loaded and loading chunks // undefined = chunk not loaded, null = chunk preloaded/prefetched // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded var installedChunks = {"main": 0 };undefined: chunk沒加載
null: chunk preloaded/prefetched
0: chunk加載完成
其中main是入口模塊,被打包進(jìn)入口chunk,所以其值是0,表示加載完成
__webpack_require__.f.j方法執(zhí)行流程:
installedChunkData中的值
=0,結(jié)束
!=0
再判斷installedChunkData = installedChunks[chunkId]的值
真值: promises.push(installedChunkData[2])
假值:開始加載chunk
構(gòu)建一個Promise實(shí)例 installedChunkData = installedChunks[chunkId] = [resolve, reject] ,并且將promise實(shí)例push到installedChunks[chunkId] 之后作為數(shù)組第三項,這樣chunkId 對應(yīng)的模塊就表示在加載中。并且將Promise實(shí)例push到promises數(shù)組中,表示這個模塊在加載了,等會吧,好了通知你。
通過chunkId構(gòu)建出文件地址url
構(gòu)建文件加載完成函數(shù)loadingEnded
__webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);
函數(shù)loadingEnded
該函數(shù)的執(zhí)行時機(jī)是完成,也就是說無論成功還是失敗都會被執(zhí)行。
如果 installedChunkData = installedChunks[chunkId]的值
如果 installedChunkData 的值(ps: 這一步和上一步是順序發(fā)生的,只是目的不同)
假值:什么都不做,因為假值是0,undefined,null,表示加載完成
真值:還在加載中,但其實(shí)文件的加載已經(jīng)失敗了。因為加載有結(jié)果了,但是沒有成功,成功的話其值會被設(shè)置成0,所以就表示失敗。這時候?qū)nstalledChunkData的值,也就是之前的數(shù)組[resolve, reject, promise]中的reject拿出來,結(jié)束這個promise。
loadingEnded函數(shù)中并沒有調(diào)用promise的resolve函數(shù),那么是在哪里調(diào)用的呢?這個要看下面的函數(shù)webpackJsonpCallback
webpackJsonpCallback
這個函數(shù)是webpackchunk加載成功的回調(diào)。
var webpackJsonpCallback = (parentChunkLoadingFunction, data) {...}var chunkLoadingGlobal = self["webpackChunkapp3"] = self["webpackChunkapp3"] || [];chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));這些代碼行數(shù)不多,但是做的事情卻不太好理解。要理解它在做什么需要了解它加載的chunk是什么形狀:
// src_bootstrap_js.js (self["webpackChunkapp3"] = self["webpackChunkapp3"] || []).push([["src_bootstrap_js"],{ "./node_modules/abc/index.js": (() => {console.log('zhouzhuoxin') }), "./src/App.js":((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {...}) }])可以觀察這兩大段代碼,
第一段:來自webpack運(yùn)行時,chunk下載相關(guān)代碼,和__webpack_require__.f.j 相關(guān)
第二段:來自被下載的chunk src_bootstrap_js.js
先看第二段代碼
self["webpackChunkapp3"] = self["webpackChunkapp3"] || []
先看下有沒有全局變量webpackChunkapp3 有的話直接使用,沒有就賦值空數(shù)組。
向數(shù)組中添加chunk數(shù)據(jù),.push([[”chunkId”], {moduleId1() {}, moduleId2() {}, ...}]) 。數(shù)組的第一項交代了這個文件中包含了哪些chunk,第二項交代了所有chunk中包含的所有模塊。
chunk是模塊的容器
再看第一段代碼
var chunkLoadingGlobal = self["webpackChunkapp3"] = self["webpackChunkapp3"] || [];
webpackChunkapp3:
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
為已經(jīng)存在的chunk調(diào)用webpackJsonpCallback 回調(diào)函數(shù),后面詳細(xì)介紹這個函數(shù)的作用。
chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
這一行最燒腦,這是一個責(zé)任鏈模式,它將chunkLoadingGlobal (這是一個數(shù)組)的push方法改寫成調(diào)用webpackJsonpCallback 方法,并且將原來的push方法(真的是數(shù)組的push方法嗎?)作為該函數(shù)的第一個入?yún)ⅰ?/p>
達(dá)到的效果就是,調(diào)用chunkLoadingGlobal.push 時會先調(diào)用webpackJsonpCallback 方法,然后在webpackJsonpCallback方法中再將chunkData push到數(shù)組chunkLoadingGlobal 中。
到這里在入口chunk加載完成之前chunkLoadingGlobal 中的數(shù)據(jù)經(jīng)歷過了webpackJsonpCallback 函數(shù),在入口chunk加載完成之后也會經(jīng)歷webpackJsonpCallback 這個函數(shù)。
webpackJsonpCallback(parentChunkLoadingFunction, data) 這個函數(shù)在做啥?
換句話說chunk加載完成后它會調(diào)用該函數(shù),只是它的調(diào)用不是在監(jiān)聽文件加載的地方,而是在加載的文件中。就是self["webpackChunkapp3"].push方法的調(diào)用。
如果加載的chunk中攜帶的chunk組中有一個chunkid沒有被安裝過
將chunk中攜帶的module,全部都重新掛載在__webpack_require__.m上。(我理解是因為無法區(qū)分這個module是哪一個chunk的,導(dǎo)致要全部重新掛載)
parentChunkLoadingFunction
-
存在:使用data調(diào)用它
-
不存在:什么都不做
開始處理這個文件中chunk組對應(yīng)的chunkIds
installedChunks中對應(yīng)chunk的值:
-
= resolve, reject, promise,調(diào)用resolve
-
installedChunks[chunkId] = 0,設(shè)置為這個chunk加載完成
到這里整個chunk加載完成,chunk中攜帶的模塊都被安裝到了__webpack_modules__中,也就是說__webpack_require__可以獲取到模塊id對用的模塊了。
總結(jié)
再簡單梳理下模塊加載整個流程如下:
// index.js import('./bootstrap.js')會被編譯成
__webpack_require__.e(/*! import() */ "src_bootstrap_js").then(__webpack_require__.bind(__webpack_require__, /*! ./bootstrap */ "./src/bootstrap.js"));方法e的入?yún)⑹莄hunkId。
以上是正常webpack運(yùn)行時的運(yùn)作流程。
總結(jié)
以上是生活随笔為你收集整理的webpack联邦模块之webpack运行时的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【antd】输入控件的思想
- 下一篇: webpack联邦模块之remotes方