欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Webpack?模塊加載動態(tài)引入機(jī)制源碼示例解析

 更新時間:2022年09月27日 11:12:17   作者:mysteryven  
這篇文章主要為大家介紹了Webpack?模塊加載動態(tài)引入機(jī)制源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

TL;DR

本文基于 Webpack 5 進(jìn)行講解,適合不了解 Webpack 把資源編譯成什么樣子的同學(xué),讀完本文,你將理解下面幾個問題的來龍去脈:

  • Webpack 靜態(tài)引入的實現(xiàn)邏輯,如 import App from './App'
  • Webpack 的動態(tài)引入原理,也就是動態(tài) import 是怎么實現(xiàn)的,如 import('./App')
  • 模塊聯(lián)邦的原理(目前只給了大體的邏輯,超過 20 個贊會補(bǔ)充這部分的內(nèi)容)

不僅如此,我們還將在每一個部分與 Vite 的實現(xiàn)進(jìn)行對比,讓大家能在更高的層次上掌握這部分知識。大多數(shù)講解 Webpack 源碼的內(nèi)容都是截圖源碼,而筆者在閱讀這些文章的時候就覺得體驗不是特別好,往往看了幾行便退出了。

本文會在保留原始函數(shù)名的基礎(chǔ)上,抽離出主要的邏輯實現(xiàn),相信這肯定能讓大家更清晰的理解。

準(zhǔn)備階段

對某些同學(xué)來說,今天內(nèi)容可能會稍微有一點難,在讀完本文之后,可能還需要自己調(diào)試一下代碼才能真正的理解,不過大家不用擔(dān)心,筆者會盡力做到講解清晰。最開始,先請大家和筆者一同配置下 Webpack 環(huán)境。

  • 安裝 pnpm(非必須,不喜歡的同學(xué)請把后面的 pnpm 替換為 npm,pnpx 替換為 npx
npm install -g pnpm
  • 初始化
mkdir webpack-demo
pnpm init
pnpm i webpack
  • 生成模板(命令行提示缺什么,按照提示安裝即可)
pnpm webpack init -f
  • 使用下面的配置替換 webpack.config.js
const config = {
    entry: './src/index.js',
    mode: 'development',
    output: {
        path: path.resolve(__dirname, 'dist'),
    },
    devServer: {
        open: true,
        host: 'localhost',
    },
    devtool: 'source-map',
    optimization: {
        runtimeChunk: 'single'
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: 'index.html',
        }),
    ],
    module: {
        rules: [
            {
                test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
                type: 'asset',
            },
        ],
    },
};
module.exports = config
  • package.json build 命令替換,不保留指定 mode 為 production。
"scripts": {
-    "build": "webpack --mode=production --node-env=production",
-    "build:dev": "webpack --mode=development",
-    "build:prod": "webpack --mode=production --node-env=production",
+    "build": "webpack",
     "serve": "webpack serve"
 },
  • 測試 pnpm buildpnpm serve 都能正常運(yùn)行,并且打包目錄能看到獨(dú)立的 runtime.js,說明已經(jīng)配置好了。

Runtime

Runtime 又叫做運(yùn)行時,它的作用是串聯(lián)起各個模塊,包括引入模塊、下載模塊、一些基礎(chǔ)的公共方法。通過 Runtime 作為橋梁,我們就能把各個模塊聯(lián)系起來,最終讓被 Webpack 打包的應(yīng)用在瀏覽器跑起來。

除此之外,HMR 的能力也需要 Runtime 的支持,我們可以通過預(yù)先注入一系列 HMR 的工具函數(shù)(包括 WebSockect 通信,HMR API),來實現(xiàn)此功能。

如果你按照我們的準(zhǔn)備階段的提示成功把項目跑起來了,可以運(yùn)行一下 pnpm build 命令,然后去 dist 目錄查看 runtime.js 文件。搜索 __webpack_require__ 關(guān)鍵詞,它下面會有很多方法或?qū)ο?,包?__webpack_require__.m、__webpack_require__.o__webpack_require__.e, 這些就是我們今天要談?wù)摰闹鹘恰?/p>

模塊被打包成了什么樣子?

這一部分我們不使用 Webpack 打包,而是模擬一下。

對于模塊被打包要解決的問題,筆者有一些思考,認(rèn)為有以下幾個方面:

  • 對 ES Module 出于兼容性的考慮,在 Webpack 出現(xiàn)的那個時代,ES Module 的支持性并不理想。
  • 在 HTTP 1.X 的場景下,ES Module 帶來的請求量不可預(yù)估,而 HTTP 層面隊頭阻塞的缺點,使得項目可能會造成網(wǎng)絡(luò)阻塞的現(xiàn)象。除此之外, 在現(xiàn)在 ESM 支持性已經(jīng)很好的場景下,即便我們使用了 HTTP 2 可以不用考慮并行的請求數(shù),但是 import 的層級嵌套依然會帶來網(wǎng)絡(luò)層面上額外的 Road Trip 的消耗,同時依然存在 TCP 層面的隊頭阻塞。
  • 對于一些相似性很高的內(nèi)容,多個文件壓縮到一塊壓縮效果也不差,可能會比兩者分開請求要好。

模塊被打包后可能需要考慮下面三點:

  • 獨(dú)立的模塊作用域,兩個模塊之間不應(yīng)該互相影響
  • 緩存機(jī)制,模塊被加載過一次就不用再發(fā)起請求了
  • 環(huán)依賴問題

在上面的基礎(chǔ)上,我們來看看 Webpack 把 ESM 的代碼編譯成立什么樣子。

首先,我們的有三個文件:index.js、message.js、name.js,依賴關(guān)系如下面代碼所示:

// filename: index.js 
// ** 入口文件 ** 
import message from './message.js';
console.log(message);
// filename: message.js
import {name} from './name.js';
export default `hello ${name}!`;
// filename: name.js
export const name = 'world';

最后的執(zhí)行結(jié)果便是輸出 hello world。

我們來看一下最后編譯成的樣子:

const modules = {
    0: [
        function (require, module, exports) {
            "use strict";
            var _message = require("./message.js");
            var _message2 = _interopRequireDefault(_message);
            function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
            console.log(_message2.default);
        },
        { "./message.js": 1 },
    ],
    1: [
        function (require, module, exports) {
            "use strict";
            Object.defineProperty(exports, "__esModule", {
                value: true
            });
            var _name = require("./name.js");
            exports.default = "hello " + _name.name + "!";
        },
        { "./name.js": 2 },
    ],
    2: [
        function (require, module, exports) {
            "use strict";
            Object.defineProperty(exports, "__esModule", {
                value: true
            });
            var name = exports.name = 'world';
        },
        {},
    ],
}
function load(modules) {
    function require(id) {
        const [fn, mapping] = modules[id];
        const module = { exports: {} };
        function localRequire(name) { return require(mapping[name]); }
        fn(localRequire, module, module.exports);
        return module.exports;
    }
    require(0);
}
load(modules)

可以看到,我們所有模塊的內(nèi)容都被維護(hù)到了 modules 這個大對象里,import 語句被轉(zhuǎn)換成立 require 語句,當(dāng)調(diào)用 load 函數(shù)的時候,整個加載過程就開始了。如果第一次了解上面的格式,可能需要大家好好的品味一下。

再次說明,上面這段代碼值得花時間好好看一下。

如果你有興趣想了解是怎么轉(zhuǎn)成這種格式的,推薦 minipack 這個庫,如果不喜歡看英文,可以看筆者的這篇 mini webpack打包基礎(chǔ)解決包緩存和環(huán)依賴

靜態(tài)引入

自從社區(qū)涌現(xiàn)了了 Vite、Snowpack 等打包工具之后,Webpack 則被分到了一個新的營地 —— Bundler,與之相對的,Vite 則是 No-Bundler。在講解完本小節(jié)內(nèi)容最后,筆者會為大家對比 Vite 和 Webpack 在引用模塊機(jī)制上的區(qū)別,屆時大家可能從引用模塊的這個角度對 Bundler 和 No-Bundler 有更深刻的理解,可能也會知道,No-Bundler 并非一定是銀彈。

在此之前,讓我們先聚焦于 Webpack 的模塊引用機(jī)制。我們使用的例子依然是上一小節(jié)的例子,只不過打包工具換成了 Webpack。

首先,我們先運(yùn)行一下 pnpm build, 發(fā)現(xiàn) dist 目錄有兩個 JS 文件:main.js、runtime.js。運(yùn)行時的代碼都在 runtime.js,而我們模塊內(nèi)容相關(guān)的都在 main.js。由 index.html 控制二者的下載:

<script defer src="runtime.js"></script>
<script defer src="main.js"></script>

可以注意到先下載了 runtime.js, 再下載 main.js。這是必須的,因為首先我們需要在注冊一些全局變量,注冊好了之后,main.js 才可以通過全局變量來和運(yùn)行時進(jìn)行交互。加 defer 的作用是可以不阻塞 DOM 樹的解析,異步下載內(nèi)容,可以減少白屏?xí)r間(First Content Paint)。

最開始的,定義的 webpackChunkmy_webpack_project 這個全局變量,如下所示:

self["webpackChunkmy_webpack_project"] 
    = self["webpackChunkmy_webpack_project"] || [];
const chunkLoadingGlobal = self["webpackChunkmy_webpack_project"]

接著重寫 webpackChunkmy_webpack_project 上的 push 方法:

chunkLoadingGlobal.push = 
    webpackJsonpCallback.bind(
        null, 
        chunkLoadingGlobal.push.bind(chunkLoadingGlobal)
    );

上面這句話的含義是:

  • push 重置為 webpackJsonpCallback 函數(shù)
  • webpackJsonpCallback 綁定參數(shù),thisnull ,但是函數(shù)的第一個參數(shù)為chunkLoadingGlobal 數(shù)組原來的的 push 方法,也就是說,調(diào)用此方法可以往 chunkLoadingGlobal 這個數(shù)組里加值。

我們可以在 window 打印這個值:

接下來我們看一下 main.js ,各位請注意,為了方便大家閱讀,把 main.js 格式修改了,如果大家看源碼建議搜索變量名。

const chunkIds = ["main"];
const moreModules = {
    "./src/index.js":
        ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
            // 內(nèi)容暫時省略
        }),
    "./src/message.js":
        ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
            // 內(nèi)容暫時省略
        }),
    "./src/name.js":
        ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
            // 內(nèi)容暫時省略
        })
}
const runtime = __webpack_require__ => {
    var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId))
    var __webpack_exports__ = (__webpack_exec__("./src/index.js"));
}
self["webpackChunkmy_webpack_project"].push([
    ["main"],
    moreModules,
    runtime
]);

所以關(guān)鍵點還是來到了調(diào)用 webpackChunkmy_webpack_projectpush 方法, 也就是 runtime 里的 webpackJsonpCallback,接下來我們看這個函數(shù)做了什么,你可以先大概瀏覽一下。

var webpackJsonpCallback = (
    parentChunkLoadingFunction,
    data
) => {
    var [chunkIds, moreModules, runtime] = data;
    var moduleId, chunkId, i = 0;
    if (chunkIds.some((id) => (installedChunks[id] !== 0))) {
        for (moduleId in moreModules) {
            if (__webpack_require__.o(moreModules, moduleId)) {
                __webpack_require__.m[moduleId] = moreModules[moduleId];
            }
        }
        if (runtime) var result = runtime(__webpack_require__);
    }
    if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
    for (; i < chunkIds.length; i++) {
        chunkId = chunkIds[i];
        if (__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
            installedChunks[chunkId][0]();
        }
        installedChunks[chunkId] = 0;
    }
    return __webpack_require__.O(result);
}

接下來逐行解釋:

1. 函數(shù)的第一個參數(shù)是綁定數(shù)組原始的 push 方法,最開始就被 bind 了; 第二個參數(shù)是我們調(diào)用此函數(shù)入的參數(shù),可以回看一下 main.js 最后的調(diào)用,是一個數(shù)組結(jié)構(gòu)。

self["webpackChunkmy_webpack_project"].push([
    ["main"],
    moreModules,
    runtime
]);

2. var [chunkIds, moreModules, runtime] = data;

解構(gòu)出這些參數(shù),其中 chunkIds["main"],剩下的以此類推。

3.

if (chunkIds.some((id) => (installedChunks[id] !== 0))) {
    for (moduleId in moreModules) {
        if (__webpack_require__.o(moreModules, moduleId)) {
            __webpack_require__.m[moduleId] = moreModules[moduleId];
        }
    }
    if (runtime) var result = runtime(__webpack_require__);
}

installedChunk 是用來緩存模塊的加載狀態(tài)的,其中 0 代表已經(jīng)加載好了。所以 if 語句的意思就是,如果 chunkIds 有模塊還沒有加載好。

繼續(xù)往下我們需要介紹兩個函數(shù)。

a. __webpack_require__.o:這個就是判斷判斷 key 值有沒有在對象本身上:

__webpack_require__.o = 
    (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))

b. __webpack_require__.m: 它維護(hù)的是所有的模塊,因為我們可能有 main.js,main-1.js 都有模塊需要管理,這時候就通過它去統(tǒng)一的注冊上我們的模塊里去。

var __webpack_modules__ = ({});
__webpack_require__.m = __webpack_modules__;

明白了上面兩個工具函數(shù),我們上面那段代碼的含義就是把 moreModules 里的模塊都注冊到 __webpack_require__.m 上去。

注冊完了之后剩下的就是執(zhí)行了,也就是 runtime 函數(shù)做的事情 runtime 函數(shù)可以簡化為 :

const runtime = __webpack_require__ => {
   __webpack_require__("./src/index.js"));
}

__webpack_require__ 的作用可以理解為和我們在 「模塊被打包成了什么樣子?」這一小節(jié)最后給出的 require 函數(shù)作用一樣了,也就是說,執(zhí)行 __webpack_require__.m 這個對象里 key 對應(yīng)的 函數(shù)。具體的,就是執(zhí)行剛才我們省略了的 moreModules 里的某一項:

"./src/index.js":
        ((_, _1, __webpack_require__) => {
            var _message_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("./src/message.js");
            console.log(_message_js__WEBPACK_IMPORTED_MODULE_0__["default"]);
        })

走到這一步,其實就已經(jīng)可以串聯(lián)起所有的模塊了。不知你是否有種撥開云霧見日出的感覺。

4. 首先是 把 data push 到我們的 webpackChunkmy_webpack_project 數(shù)組里,再接下來把加載好的做緩存,存儲到 installedChunks 中去,返回值我們沒有用到,所以這里就略過。

if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
for (; i < chunkIds.length; i++) {
    chunkId = chunkIds[i];
    // 這一步是動態(tài)引入的關(guān)鍵,這里暫時不分析
    if (__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
        installedChunks[chunkId][0]();
    }
    installedChunks[chunkId] = 0;
}
return __webpack_require__.O(result);

通過上面我們可以感受到 Webpack 是怎么加載模塊的,那在 Vite 中會是怎么樣呢?還是舉例子來說。

假設(shè)我們要請求一個 index.tsx 文件。在沒有發(fā)起請求之前,Vite 的 Dev Server 不會幫我們預(yù)編譯這個模塊。當(dāng)我們發(fā)起請求,此次 Vite 才會調(diào)用 ESBuild 編譯這個模塊,在這個過程中,會把 index.tsx 文件轉(zhuǎn)譯成 ESM 格式的 JS 文件,如果此時有 bare import ,還會有路徑名重寫成預(yù)編譯的路徑;有配置 alias ,也會把路徑名改寫成我們配置的 alias。最后,會把這個文件(index.tsx)的編譯結(jié)果記錄到 moduleGraph 中,moduleGraph 是 Vite 內(nèi)部的模塊圖,是實現(xiàn)模塊緩存、HMR 的關(guān)鍵。

接著,它會返回當(dāng)前請求,當(dāng)瀏覽器收到當(dāng)前請求之后,當(dāng)前文件可能有多個 import 請求,瀏覽器將并行的發(fā)出這些請求,Vite 也將重復(fù)上面操作,繼續(xù)使用 ESBuild 的編譯,然后記錄到 moduleGraph 中,一直遞歸的進(jìn)行到當(dāng)前入口文件都請求完畢。

等到再次請求,我們就可以沒有這么麻煩了,直接從 moduleGraph 中讀取緩存的內(nèi)容了。

大家可能發(fā)現(xiàn)了,從加載模塊的這個角度,moduleGraph 和我們上面的 __webpack_require__.m 基本是一樣的。只不過,Webpack 事先計算好了所有的內(nèi)容,而 Vite 則按需計算。如果我們不做分包,一個很大的文件嵌套很多層,在 Dev Server 階段最開始啟動服務(wù)的時候, Vite 也并不一定比 Webpack 要快。

由于目前 Vite 沒有對 moduleGraph 做緩存,重啟 Server 則又會重新走一步編譯-存儲的流程,所以每次重啟,在極端情況下,第一次加載可能都會比較慢;與之相對的,Webpack 現(xiàn)在對編譯的結(jié)果做文件系統(tǒng)級別的緩存,這樣子做了之后,甚至可以逼近 No-Bundler 的速度了。

module.exports = {
  cache: {
    type: 'filesystem',
    allowCollectingMemory: true,
  },
};

動態(tài)引入

通過上面那一小節(jié)的講解,或許你會發(fā)現(xiàn),假設(shè)我們動態(tài)引入的模塊叫做 a.js,只要它也是形如這樣去調(diào)用的:

// ... 前面省略
self["webpackChunkmy_webpack_project"].push([
    ["a"],
    moreModules,
    runtime
]);

我們好像不用額外做什么,就能把這些模塊注冊到主模塊并且運(yùn)行了。又可以說,某種程度上我們的靜態(tài)引入其實也算是「動態(tài)引入」。當(dāng)然了,其實我們還是要做一些額外的工作的,因為我們的模塊引入之后,往往有一些回調(diào)函數(shù)。但是主要的邏輯基本是一致的。

其實動態(tài)引入(Dynamic Import)的實現(xiàn)一般都是通過 JSONP 的形式,基本的實現(xiàn)原理如下所示:

function importModule(url) {
  return new Promise((resolve, reject) => {
    const script = document.createElement("script");
    // 注冊一個隨機(jī)的全局變量,后面值掛上去
    const tempGlobal = "__tempModuleLoadingVariable" + Math.random().toString(32).substring(2);
    script.type = "module";
    script.textContent = `import * as m from "${url}"; window.${tempGlobal} = m;`;
    script.onload = () => {
      // 請求結(jié)束 resolve 掉。
      resolve(window[tempGlobal]);
      delete window[tempGlobal];
      script.remove();
    };
    script.onerror = () => {
      reject(new Error("Failed to load module script with URL " + url));
      delete window[tempGlobal];
      script.remove();
    };
    document.documentElement.appendChild(script);
  });
}

動態(tài) Import 提案

接下來我們改一下例子,運(yùn)行 pnpm build 看看在 Webpack 中是怎么實現(xiàn)的:

// filename: index.js
- import message from './message.js';
- console.log(message);
+ import ('./message.js').then(message => {
+     console.log(message)
+ })

首先我們發(fā)現(xiàn)多了一個文件 src_message_js.js。

再觀察 main.js,import 語句被編譯成了:

__webpack_require__.e(
    "src_message_js"
).then(
    __webpack_require__.bind(
        __webpack_require__,
        "./src/message.js"
   )
).then(message => {
    console.log(message)
})

看這段代碼我們根據(jù)前面的知識可以進(jìn)行猜想:

  • __webpack_require__.e 的作用就是下載 src_message_js 的內(nèi)容,并把它注冊到全局的 __webpack_require__.m 上,這一步應(yīng)該可以靜態(tài)引入一樣,不過它是異步完成,下載完了才注冊。
  • 調(diào)用__webpack_require__(./src/message.js) 可以從全局模塊對象里拿到它對應(yīng)的導(dǎo)出值
  • 打印出結(jié)果

接下來我們開始實際調(diào)試驗證對不對。

首先是 __webpack_require__.e,它的作用就是遍歷執(zhí)行 __webpack_require__.f 上的方法,并且要再它上面所有的方法都執(zhí)行完了才解決:

__webpack_require__.e = (chunkId) => {
    return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
        __webpack_require__.f[key](chunkId, promises);
        return promises;
    }, []));
};

值得高興的是,__webpack_require__.f就掛了一個方法,__webpack_require__.f.j。它的作用就是通過 JSONP 的形式下載模塊。

在這里先和大家講解一下 installedChunks 的數(shù)據(jù)結(jié)構(gòu),講完這個,大家應(yīng)該可以結(jié)合著筆者給的注釋看明白代碼了。它也是一個緩存的對象,為了避免多次動態(tài)引入而發(fā)起多次請求。

  • 第一種情況,已經(jīng)安裝的模塊,value 會被置為 0,這樣再引入便直接使用緩存。
const installedChunks = {
    runtime: 0
}
  • 第二種情況,還在加載中的模塊,value 是一個數(shù)組,三項分別是同一個 promise 對象的 resolve、reject、本身:
const installedChunks = {
    'src_message_js': [resolve, reject, promise]
}

接下來大家看代碼不要糾結(jié)于細(xì)節(jié),而是抓住主干:在請求到資源后,調(diào)用 onload 事件,整個流程就完了。

__webpack_require__.f.j = (chunkId, promises) => {
    var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
    if (installedChunkData !== 0) { // 0 代表已經(jīng)下載好了.
        // 有值說明還在 loading 中,已經(jīng)開始下載了,沒必要再次下載
        // 但是把 promise 狀態(tài)給出去
        if (installedChunkData) {
            promises.push(installedChunkData[2]);
        } else {
            if ("runtime" != chunkId) {
                // 組裝好 promise, 填入 installedChunks 中
                var promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));
                promises.push(installedChunkData[2] = promise);
                // 根據(jù) publicPath 拼出 合適的 URL
                var url = __webpack_require__.p + __webpack_require__.u(chunkId);
                var error = new Error();
                var loadingEnded = (event) => {
                    // 意義不大,暫時刪掉
                };
                __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);
            } else installedChunks[chunkId] = 0; // 標(biāo)識已 loaded
        }
    }
};

接下來就是 __webpack_require__.l 這個工具函數(shù),它的作用就是根據(jù)傳入的 URL 去請求,簡化后如下:

__webpack_require__.l = (url, done, key, chunkId) => {
    var scripts = document.getElementsByTagName("script");
    script.timeout = 120;
    script.src = url;		
    document.head.appendChild(script)
}

執(zhí)行到這里 就回去下載 src_message_js.js 文件,你可能會好奇,怎么還沒 resolve ? 其實 src_message_js.js 的加載方式和我們剛開始說的靜態(tài)引入一樣,也是調(diào)用 push 方法:

self["webpackChunkmy_webpack_project"] || []).push([
    ["src_message_js"], 
    // 后面省略
 )

而此時就又會走到 webpackJsonpCallback,在調(diào)用這個函數(shù)的時候,我們回顧一下,會先把模塊注冊到 __webpack_require__.m 上去,接下來有一點我們沒有講:

if (
    __webpack_require__.o(installedChunks, chunkId) 
    && installedChunks[chunkId]
) {
    installedChunks[chunkId][0]();
}

上面代碼就是關(guān)鍵,在這里去 resolve 掉了我們的異步引入,此時我們再把整理的函數(shù)放過來,你是不是對這個函數(shù)的每一行都理解了呢:

var webpackJsonpCallback = (
    parentChunkLoadingFunction,
    data
) => {
    var [chunkIds, moreModules, runtime] = data;
    var moduleId, chunkId, i = 0;
    if (chunkIds.some((id) => (installedChunks[id] !== 0))) {
        for (moduleId in moreModules) {
            if (__webpack_require__.o(moreModules, moduleId)) {
                __webpack_require__.m[moduleId] = moreModules[moduleId];
            }
        }
        if (runtime) var result = runtime(__webpack_require__);
    }
    if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
    for (; i < chunkIds.length; i++) {
        chunkId = chunkIds[i];
        if (__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
            installedChunks[chunkId][0]();
        }
        installedChunks[chunkId] = 0;
    }
    return __webpack_require__.O(result);
}

好了,這就是 Webpack 動態(tài)引入的原理。筆者認(rèn)為實現(xiàn)過程非常美麗、優(yōu)雅。而在 Vite 中其實就沒什么好說的了,它就是使用的原生的 import 。

模塊聯(lián)邦引入原理

這個的實現(xiàn)邏輯和動態(tài)引入很像,不同的是,動態(tài)引入只有一個工具函數(shù),叫做 __webpack_require__.f.j,而它有三個,分別是:

__webpack_require__.f.consumes

__webpack_require__.f.j

__webpack_require__.f.remotes

由這三個一起完成了模塊聯(lián)邦的神奇功能。

這部分容筆者留一個坑,其實,再講解完上面兩部分之后,同學(xué)們可以自己嘗試一下是否可以明白原理了,如果有同學(xué)想看這部分原理,不妨留言,筆者擇期進(jìn)行補(bǔ)充。

和各位讀者預(yù)告一下,下一篇,筆者將更新 Immer 的源碼解讀,這是一個筆者很喜歡的庫,敬請期待。

更多關(guān)于Webpack 模塊加載動態(tài)引入的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論