動態(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)不是一個靜態(tài)字符串,而是一個動態(tài)表達(dá)式時,構(gòu)建工具其實(shí)也不確定用戶到底引用了什么,所以通常這種情況只能依靠 JavaScript 的運(yùn)行時來解析。
若動態(tài)表達(dá)式實(shí)際代表的路徑無法被解析,則運(yùn)行時會引起控制臺的錯誤。通常是因?yàn)樯傻奈募窂讲]有被納入打包體系,所以找不到文件。
下面列出了一些常見的動態(tài)引入表達(dá)式:
// TemplateLiteral 模板字符串 import(`./icons/arrow-${type}.svg`); require(`./icons/arrow-${type}.svg`); // BinaryExpression 二元表達(dá)式 import('./icon/arrow-' + type + '.svg'); // 直接引用一個變量 import(path); require(path)
但經(jīng)過前人們的實(shí)踐發(fā)現(xiàn),當(dāng)動態(tài)表達(dá)式滿足一定的結(jié)構(gòu)時,構(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)入的路徑中包含變量時,經(jīng)過 AST 分析可以生成對應(yīng)的通配符。在構(gòu)建的時候,這些通配符將被用于抓取匹配的文件。隨后這些文件會被添加進(jìn)構(gòu)建體系中,在運(yùn)行時,根據(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)行時拼接的具體字符串返回對應(yīng)的打包文件。
動態(tài)引入的限制
本節(jié)內(nèi)容翻譯加工自 @rollup/plugin-dynamic-import-vars README.md 部分章節(jié)
為了知道要在代碼中注入什么,我們必須能夠?qū)Υa進(jìn)行一些靜態(tài)分析,并對可能的導(dǎo)入做出一些假設(shè)。例如,如果只使用一個變量,理論上可以從整個文件系統(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)入的文件,包括書寫代碼的這個文件本身。因此這種情況下需要給出一個更具體的文件名匹配格式:
import(`./${foo}.js`); // not allowed import(`./module-${foo}.js`); // allowed
通配符(Glob Pattern)僅有一層深度
在生成通配符時,字符串中的每個變量都會被轉(zhuǎn)換為通配符中的*,每個層級的目錄最多一個星號。這避免了無意中從更多的目錄中添加文件到導(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í)行上述限制條件的判斷,嘗試獲取一個合法的通配符。
如果通配符不合法,將會引發(fā)錯誤,終止進(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)文章
本地搭建微信小程序服務(wù)器的實(shí)現(xiàn)方法
這篇文章主要介紹了本地搭建微信小程序服務(wù)器的實(shí)現(xiàn)方法的相關(guān)資料,希望通過本文能幫助到大家,讓大家輕松的搭建自己的微信小程序的服務(wù)器,需要的朋友可以參考下2017-10-10解析JS參數(shù)parseInt('012',?16)和parseInt(012,?16)是否相等
這篇文章主要為大家介紹了parseInt('012',?16)和parseInt(012,?16)是否相等原理解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02微信小程序 獲取設(shè)備信息 API實(shí)例詳解
這篇文章主要介紹了微信小程序 獲取設(shè)備信息 API實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2016-10-10微信小程序開發(fā)之相冊選擇和拍照詳解及實(shí)例代碼
這篇文章主要介紹了微信小程序開發(fā)之相冊選擇和拍照詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02微信小程序 網(wǎng)絡(luò)API 上傳、下載詳解
這篇文章主要介紹了微信小程序 網(wǎng)絡(luò)API 上傳、下載詳解的相關(guān)資料,需要的朋友可以參考下2016-11-11微信小程序 定義全局?jǐn)?shù)據(jù)、函數(shù)復(fù)用、模版等詳細(xì)介紹
這篇文章主要介紹了微信小程序 定義全局?jǐn)?shù)據(jù)、函數(shù)復(fù)用、模版等詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2016-10-10