Webpack?模塊加載動態(tài)引入機(jī)制源碼示例解析
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 build
、pnpm 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ù),this
為null
,但是函數(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_project
的 push
方法, 也就是 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); }); }
接下來我們改一下例子,運(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)文章
JavaScript學(xué)習(xí)筆記之取值函數(shù)getter與取值函數(shù)setter詳解
這篇文章主要介紹了JavaScript取值函數(shù)getter與取值函數(shù)setter,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08代碼規(guī)范需要防微杜漸code?review6個小錯誤糾正
這篇文章主要為大家介紹了代碼規(guī)范需要防微杜漸code?review中的6個小錯誤糾正,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06JavaScript進(jìn)制數(shù)之間的互相轉(zhuǎn)換
這篇文章主要介紹了JavaScript進(jìn)制數(shù)之間的互相轉(zhuǎn)換,進(jìn)制轉(zhuǎn)換是人們利用符號來計數(shù)的方法,下文基于JavaScript實現(xiàn)進(jìn)制數(shù)之間的轉(zhuǎn)換,有一定的知識性參考價值,需要的小伙伴可以參考一下2022-05-05