動態(tài)引入DynamicImport實(shí)現(xiàn)原理
什么是動態(tài)引入(DynamicImport)?
本文介紹的動態(tài)引入實(shí)現(xiàn)方式基于 rollup 插件 @rollup/plugin-dynamic-import-vars
通常情況下,我們都是通過確定的字面量路徑來引用文件模塊的,例如:
import './a.js';
require('./a.js');
import('./a.js');
對于確定的文件路徑來說,構(gòu)建工具可以輕易的抓取文件并進(jìn)行相關(guān)的轉(zhuǎn)換。
但當(dāng)import或者require的目標(biāo)不是一個(gè)靜態(tài)字符串,而是一個(gè)動態(tài)表達(dá)式時(shí),構(gòu)建工具其實(shí)也不確定用戶到底引用了什么,所以通常這種情況只能依靠 JavaScript 的運(yùn)行時(shí)來解析。
若動態(tài)表達(dá)式實(shí)際代表的路徑無法被解析,則運(yùn)行時(shí)會引起控制臺的錯(cuò)誤。通常是因?yàn)樯傻奈募窂讲]有被納入打包體系,所以找不到文件。
下面列出了一些常見的動態(tài)引入表達(dá)式:
// TemplateLiteral 模板字符串
import(`./icons/arrow-${type}.svg`);
require(`./icons/arrow-${type}.svg`);
// BinaryExpression 二元表達(dá)式
import('./icon/arrow-' + type + '.svg');
// 直接引用一個(gè)變量
import(path);
require(path)
但經(jīng)過前人們的實(shí)踐發(fā)現(xiàn),當(dāng)動態(tài)表達(dá)式滿足一定的結(jié)構(gòu)時(shí),構(gòu)建工具便可以通過一些特殊手段抓取并打包路徑匹配的相關(guān)文件,并自動注入一些 polyfill,從而實(shí)現(xiàn)動態(tài)引入(DynamicImport)的效果,也就是本文的主題。
動態(tài)引入的實(shí)現(xiàn)原理
本節(jié)內(nèi)容翻譯加工自 @rollup/plugin-dynamic-import-vars README.md 部分章節(jié)
當(dāng)動態(tài)導(dǎo)入的路徑中包含變量時(shí),經(jīng)過 AST 分析可以生成對應(yīng)的通配符。在構(gòu)建的時(shí)候,這些通配符將被用于抓取匹配的文件。隨后這些文件會被添加進(jìn)構(gòu)建體系中,在運(yùn)行時(shí),根據(jù)導(dǎo)入的實(shí)際路徑返回對應(yīng)的文件內(nèi)容。
下面是一些通配符的轉(zhuǎn)換示例:
`./locales/${locale}.js` -> './locales/*.js'
`./${folder}/${name}.js` -> './*/*.js'
`./module-${name}.js` -> './module-*.js'
`./modules-${name}/index.js` -> './modules-*/index.js'
'./locales/' + locale + '.js' -> './locales/*.js'
'./locales/' + locale + foo + bar '.js' -> './locales/*.js'
'./locales/' + `${locale}.js` -> './locales/*.js'
'./locales/' + `${foo + bar}.js` -> './locales/*.js'
'./locales/'.concat(locale, '.js') -> './locales/*.js'
'./'.concat(folder, '/').concat(name, '.js') -> './*/*.js'
待轉(zhuǎn)換的代碼可能是這樣的:
function importLocale(locale) {
return import(`./locales/${locale}.js`);
}
經(jīng)過轉(zhuǎn)換后它會變成下面這樣:
function __variableDynamicImportRuntime__(path) {
switch (path) {
case './locales/en-GB.js':
return import('./locales/en-GB.js');
case './locales/en-US.js':
return import('./locales/en-US.js');
case './locales/nl-NL.js':
return import('./locales/nl-NL.js');
default:
return new Promise(function (resolve, reject) {
queueMicrotask(reject.bind(null, new Error('Unknown variable dynamic import: ' + path)));
});
}
}
function importLocale(locale) {
return __variableDynamicImportRuntime__(`./locales/${locale}.js`);
}
可以看到,實(shí)際的 import 被替換成了注入的 __variableDynamicImportRuntime__ 函數(shù),該函數(shù)會根據(jù)運(yùn)行時(shí)拼接的具體字符串返回對應(yīng)的打包文件。
動態(tài)引入的限制
本節(jié)內(nèi)容翻譯加工自 @rollup/plugin-dynamic-import-vars README.md 部分章節(jié)
為了知道要在代碼中注入什么,我們必須能夠?qū)Υa進(jìn)行一些靜態(tài)分析,并對可能的導(dǎo)入做出一些假設(shè)。例如,如果只使用一個(gè)變量,理論上可以從整個(gè)文件系統(tǒng)中導(dǎo)入任何內(nèi)容。
function importModule(path) {
return import(path); // 這根本無法推斷引入了什么
}
為了能夠?qū)崿F(xiàn)靜態(tài)分析,并避免可能出現(xiàn)的問題,動態(tài)引入的實(shí)現(xiàn)上限定了一些規(guī)則:
Import 路徑須為相對路徑
所有導(dǎo)入都必須相對于導(dǎo)入文件進(jìn)行。導(dǎo)入不應(yīng)該是純變量、絕對路徑或裸導(dǎo)入:
// Not allowed
import(bar); // 純變量
import(`/foo/${bar}.js`); // 絕對路徑
import(`${bar}.js`); // 裸導(dǎo)入
import(`some-library/${bar}.js`); // 裸導(dǎo)入
引用路徑需包含文件后綴
文件夾中可能包含你不打算導(dǎo)入的文件。因此,我們要求導(dǎo)入的靜態(tài)部分以文件擴(kuò)展名結(jié)束。
import(`./foo/${bar}`); // Not allowed
import(`./foo/${bar}.js`); // Allowed
導(dǎo)入當(dāng)前目錄的文件需要指定具體的文件匹配格式
如果你從當(dāng)前目錄導(dǎo)入文件,很可能會導(dǎo)入一些原本不打算導(dǎo)入的文件,包括書寫代碼的這個(gè)文件本身。因此這種情況下需要給出一個(gè)更具體的文件名匹配格式:
import(`./${foo}.js`); // not allowed
import(`./module-${foo}.js`); // allowed
通配符(Glob Pattern)僅有一層深度
在生成通配符時(shí),字符串中的每個(gè)變量都會被轉(zhuǎn)換為通配符中的*,每個(gè)層級的目錄最多一個(gè)星號。這避免了無意中從更多的目錄中添加文件到導(dǎo)入中。
下面的例子中,最終將會生成 ./foo/*/.js 而非 ./foo/**/.js。
import(`./foo/${x}${y}/${z}.js`);
核心流程解讀
插件核心轉(zhuǎn)換代碼僅有 100 行,且非常易懂 —— plugins/index.js at master · rollup/plugins
整體流程分為以下幾步:
通過 AST 分析,拿到對應(yīng)的導(dǎo)入路徑,也就是 import 表達(dá)式括號中的源碼部分。
對這部分的源碼進(jìn)行處理,調(diào)用 dynamicImportToGlob 函數(shù)
執(zhí)行上述限制條件的判斷,嘗試獲取一個(gè)合法的通配符。
如果通配符不合法,將會引發(fā)錯(cuò)誤,終止進(jìn)程。
執(zhí)行通配符,抓取相關(guān)文件。
替換 import 表達(dá)式,并注入 __variableDynamicImportRuntime__ 函數(shù)。
附上插件核心轉(zhuǎn)換代碼的截圖,代碼本身不長且非常容易理解,感興趣的同學(xué)可以自行跳轉(zhuǎn)研究。

以上就是動態(tài)引入DynamicImport實(shí)現(xiàn)原理的詳細(xì)內(nèi)容,更多關(guān)于動態(tài)引入DynamicImport的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
js c++ vue方法與數(shù)據(jù)交互通信示例
這篇文章主要為大家介紹了js c++ vue方法與數(shù)據(jù)交互通信示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08

