動(dòng)態(tài)引入DynamicImport實(shí)現(xiàn)原理
什么是動(dòng)態(tài)引入(DynamicImport)?
本文介紹的動(dòng)態(tài)引入實(shí)現(xiàn)方式基于 rollup 插件 @rollup/plugin-dynamic-import-vars
通常情況下,我們都是通過確定的字面量路徑來引用文件模塊的,例如:
import './a.js'; require('./a.js'); import('./a.js');
對(duì)于確定的文件路徑來說,構(gòu)建工具可以輕易的抓取文件并進(jìn)行相關(guān)的轉(zhuǎn)換。
但當(dāng)import或者require的目標(biāo)不是一個(gè)靜態(tài)字符串,而是一個(gè)動(dòng)態(tài)表達(dá)式時(shí),構(gòu)建工具其實(shí)也不確定用戶到底引用了什么,所以通常這種情況只能依靠 JavaScript 的運(yùn)行時(shí)來解析。
若動(dòng)態(tài)表達(dá)式實(shí)際代表的路徑無法被解析,則運(yùn)行時(shí)會(huì)引起控制臺(tái)的錯(cuò)誤。通常是因?yàn)樯傻奈募窂讲]有被納入打包體系,所以找不到文件。
下面列出了一些常見的動(dòng)態(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)動(dòng)態(tài)表達(dá)式滿足一定的結(jié)構(gòu)時(shí),構(gòu)建工具便可以通過一些特殊手段抓取并打包路徑匹配的相關(guān)文件,并自動(dòng)注入一些 polyfill,從而實(shí)現(xiàn)動(dòng)態(tài)引入(DynamicImport)的效果,也就是本文的主題。
動(dòng)態(tài)引入的實(shí)現(xiàn)原理
本節(jié)內(nèi)容翻譯加工自 @rollup/plugin-dynamic-import-vars README.md 部分章節(jié)
當(dāng)動(dòng)態(tài)導(dǎo)入的路徑中包含變量時(shí),經(jīng)過 AST 分析可以生成對(duì)應(yīng)的通配符。在構(gòu)建的時(shí)候,這些通配符將被用于抓取匹配的文件。隨后這些文件會(huì)被添加進(jìn)構(gòu)建體系中,在運(yùn)行時(shí),根據(jù)導(dǎo)入的實(shí)際路徑返回對(duì)應(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)換后它會(huì)變成下面這樣:
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ù)會(huì)根據(jù)運(yùn)行時(shí)拼接的具體字符串返回對(duì)應(yīng)的打包文件。
動(dòng)態(tài)引入的限制
本節(jié)內(nèi)容翻譯加工自 @rollup/plugin-dynamic-import-vars README.md 部分章節(jié)
為了知道要在代碼中注入什么,我們必須能夠?qū)Υa進(jìn)行一些靜態(tài)分析,并對(duì)可能的導(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)的問題,動(dòng)態(tài)引入的實(shí)現(xiàn)上限定了一些規(guī)則:
Import 路徑須為相對(duì)路徑
所有導(dǎo)入都必須相對(duì)于導(dǎo)入文件進(jìn)行。導(dǎo)入不應(yīng)該是純變量、絕對(duì)路徑或裸導(dǎo)入:
// Not allowed import(bar); // 純變量 import(`/foo/${bar}.js`); // 絕對(duì)路徑 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)入文件,很可能會(huì)導(dǎo)入一些原本不打算導(dǎo)入的文件,包括書寫代碼的這個(gè)文件本身。因此這種情況下需要給出一個(gè)更具體的文件名匹配格式:
import(`./${foo}.js`); // not allowed import(`./module-${foo}.js`); // allowed
通配符(Glob Pattern)僅有一層深度
在生成通配符時(shí),字符串中的每個(gè)變量都會(huì)被轉(zhuǎn)換為通配符中的*,每個(gè)層級(jí)的目錄最多一個(gè)星號(hào)。這避免了無意中從更多的目錄中添加文件到導(dǎo)入中。
下面的例子中,最終將會(huì)生成 ./foo/*/.js
而非 ./foo/**/.js
。
import(`./foo/${x}${y}/${z}.js`);
核心流程解讀
插件核心轉(zhuǎn)換代碼僅有 100 行,且非常易懂 —— plugins/index.js at master · rollup/plugins
整體流程分為以下幾步:
通過 AST 分析,拿到對(duì)應(yīng)的導(dǎo)入路徑,也就是 import 表達(dá)式括號(hào)中的源碼部分。
對(duì)這部分的源碼進(jìn)行處理,調(diào)用 dynamicImportToGlob 函數(shù)
執(zhí)行上述限制條件的判斷,嘗試獲取一個(gè)合法的通配符。
如果通配符不合法,將會(huì)引發(fā)錯(cuò)誤,終止進(jìn)程。
執(zhí)行通配符,抓取相關(guān)文件。
替換 import 表達(dá)式,并注入 __variableDynamicImportRuntime__
函數(shù)。
附上插件核心轉(zhuǎn)換代碼的截圖,代碼本身不長且非常容易理解,感興趣的同學(xué)可以自行跳轉(zhuǎn)研究。
以上就是動(dòng)態(tài)引入DynamicImport實(shí)現(xiàn)原理的詳細(xì)內(nèi)容,更多關(guān)于動(dòng)態(tài)引入DynamicImport的資料請(qǐng)關(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ā)之相冊(cè)選擇和拍照詳解及實(shí)例代碼
這篇文章主要介紹了微信小程序開發(fā)之相冊(cè)選擇和拍照詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-02-02微信小程序 網(wǎng)絡(luò)API 上傳、下載詳解
這篇文章主要介紹了微信小程序 網(wǎng)絡(luò)API 上傳、下載詳解的相關(guān)資料,需要的朋友可以參考下2016-11-11vue axios請(qǐng)求超時(shí)的正確處理方法
這篇文章主要介紹了vue axios請(qǐng)求超時(shí),設(shè)置重新請(qǐng)求的完美解決方法,一并給大家介紹了axios基本用法,需要的朋友可以參考下2018-04-04微信小程序 定義全局?jǐn)?shù)據(jù)、函數(shù)復(fù)用、模版等詳細(xì)介紹
這篇文章主要介紹了微信小程序 定義全局?jǐn)?shù)據(jù)、函數(shù)復(fù)用、模版等詳細(xì)介紹的相關(guān)資料,需要的朋友可以參考下2016-10-10