动态引入DynamicImport实现原理

 更新时间:2023年1月10日 15:10  点击:64 作者:情绪羊

什么是动态引入(DynamicImport)?

本文介绍的动态引入实现方式基于 rollup 插件 @rollup/plugin-dynamic-import-vars

通常情况下,我们都是通过确定的字面量路径来引用文件模块的,例如:

import './a.js';
require('./a.js');
import('./a.js');

对于确定的文件路径来说,构建工具可以轻易的抓取文件并进行相关的转换。

但当import或者require的目标不是一个静态字符串,而是一个动态表达式时,构建工具其实也不确定用户到底引用了什么,所以通常这种情况只能依靠 JavaScript 的运行时来解析。

若动态表达式实际代表的路径无法被解析,则运行时会引起控制台的错误。通常是因为生成的文件路径并没有被纳入打包体系,所以找不到文件。

下面列出了一些常见的动态引入表达式:

// TemplateLiteral 模板字符串
import(`./icons/arrow-${type}.svg`);
require(`./icons/arrow-${type}.svg`);
// BinaryExpression 二元表达式
import('./icon/arrow-' + type + '.svg');
// 直接引用一个变量
import(path);
require(path)

但经过前人们的实践发现,当动态表达式满足一定的结构时,构建工具便可以通过一些特殊手段抓取并打包路径匹配的相关文件,并自动注入一些 polyfill,从而实现动态引入(DynamicImport)的效果,也就是本文的主题。

动态引入的实现原理

本节内容翻译加工自 @rollup/plugin-dynamic-import-vars README.md 部分章节

当动态导入的路径中包含变量时,经过 AST 分析可以生成对应的通配符。在构建的时候,这些通配符将被用于抓取匹配的文件。随后这些文件会被添加进构建体系中,在运行时,根据导入的实际路径返回对应的文件内容。

下面是一些通配符的转换示例:

`./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'

待转换的代码可能是这样的:

function importLocale(locale) {
  return import(`./locales/${locale}.js`);
}

经过转换后它会变成下面这样:

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`);
}

可以看到,实际的 import 被替换成了注入的 __variableDynamicImportRuntime__ 函数,该函数会根据运行时拼接的具体字符串返回对应的打包文件。

动态引入的限制

本节内容翻译加工自 @rollup/plugin-dynamic-import-vars README.md 部分章节

为了知道要在代码中注入什么,我们必须能够对代码进行一些静态分析,并对可能的导入做出一些假设。例如,如果只使用一个变量,理论上可以从整个文件系统中导入任何内容。

function importModule(path) {
  return import(path); // 这根本无法推断引入了什么
}

为了能够实现静态分析,并避免可能出现的问题,动态引入的实现上限定了一些规则:

Import 路径须为相对路径

所有导入都必须相对于导入文件进行。导入不应该是纯变量、绝对路径或裸导入:

// Not allowed
import(bar); // 纯变量
import(`/foo/${bar}.js`); // 绝对路径
import(`${bar}.js`); // 裸导入
import(`some-library/${bar}.js`); // 裸导入

引用路径需包含文件后缀

文件夹中可能包含你不打算导入的文件。因此,我们要求导入的静态部分以文件扩展名结束

import(`./foo/${bar}`); // Not allowed
import(`./foo/${bar}.js`); // Allowed

导入当前目录的文件需要指定具体的文件匹配格式

如果你从当前目录导入文件,很可能会导入一些原本不打算导入的文件,包括书写代码的这个文件本身。因此这种情况下需要给出一个更具体的文件名匹配格式:

import(`./${foo}.js`); // not allowed
import(`./module-${foo}.js`); // allowed

通配符(Glob Pattern)仅有一层深度

在生成通配符时,字符串中的每个变量都会被转换为通配符中的*,每个层级的目录最多一个星号。这避免了无意中从更多的目录中添加文件到导入中。

下面的例子中,最终将会生成 ./foo/*/.js 而非 ./foo/**/.js

import(`./foo/${x}${y}/${z}.js`);

核心流程解读

插件核心转换代码仅有 100 行,且非常易懂 —— plugins/index.js at master · rollup/plugins

整体流程分为以下几步:

通过 AST 分析,拿到对应的导入路径,也就是 import 表达式括号中的源码部分。

对这部分的源码进行处理,调用 dynamicImportToGlob 函数

执行上述限制条件的判断,尝试获取一个合法的通配符。

如果通配符不合法,将会引发错误,终止进程。

执行通配符,抓取相关文件。

替换 import 表达式,并注入 __variableDynamicImportRuntime__ 函数。

附上插件核心转换代码的截图,代码本身不长且非常容易理解,感兴趣的同学可以自行跳转研究。

以上就是动态引入DynamicImport实现原理的详细内容,更多关于动态引入DynamicImport的资料请关注猪先飞其它相关文章!

原文出处:https://juejin.cn/post/7186172256827998266

相关文章

  • PHP的运行机制与原理(底层)

    说到php的运行机制还要先给大家介绍php的模块,PHP总共有三个模块:内核、Zend引擎、以及扩展层;PHP内核用来处理请求、文件流、错误处理等相关操作;Zend引擎(ZE)用以将源文件转换成机器语言,然后在虚拟机上运行它;扩展层是一组...2015-11-24
  • C# URL短地址压缩算法及短网址原理解析

    这篇文章主要介绍了C# URL短地址压缩算法及短网址原理解析,本文重点给出了算法代码,需要的朋友可以参考下...2020-06-25
  • java中Base64编码原理实例讲解

    这篇文章主要介绍了java中Base64编码原理实例讲解,文章讲解的很清晰,有对于这方面不太懂的同学可以研究下...2021-02-10
  • Python基础之Socket通信原理

    这篇文章主要介绍了Python基础之Socket通信原理,文中有非常详细的代码示例,对正在学习python基础的小伙伴们有非常好的帮助,需要的朋友可以参考下...2021-04-22
  • java进阶之了解SpringBoot的配置原理

    今天带大家了解SpringBoot的相关知识,文中对SpringBoot的配置原理作了非常详细的图文示例及介绍,需要的朋友可以参考下...2021-06-04
  • c病毒程序原理分析(防范病毒 c语言小病毒示例)

    这篇文章主要介绍了病毒程序原理,写个小程序做演示,大家可以参考这个以防中相似C病毒...2020-04-25
  • Vue中key的作用及原理详解

    本文主要介绍了Vue3中key的作用和工作原理,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-10-13
  • php缓存技术原理详细说明与实例(1/15)

    缓存技术: 有些信息比方经常不变的,但是还是能变的信息放在缓存中以加快显示速度,这是很有价值的,所谓的缓存,通俗的理解就是一些保存在服务器端的共用信息.它是...2016-11-25
  • C指针原理教程之编译原理-小型计算器实现

    本文给大家分享的是如何使用C语言编写一个小型计算器的实例代码,有需要的小伙伴可以参考下...2020-04-25
  • php怎么运行的?php运行原理

    这篇文章研究了php代码是如何解释和执行以及PHP脚本运行的生命周期,有兴趣的同学可以看看。 概述PHP服务的启动。严格来说,PHP的相关进程是不需要手动启动的,它是随...2017-07-06
  • 解析Tomcat架构原理到架构设计

    一般学习的时候也是先总览一下整体,然后逐个部分个个击破,最后形成思路,了解具体细节,Tomcat的结构很复杂,但是Tomcat非常的模块化,找到了Tomcat最核心的模块,问题才可以游刃而解,了解了Tomcat的整体架构对以后深入了解Tomcat来说至关重要...2021-06-23
  • Cookie的工作原理和应用详解

    Cookies是 web站点放置到你的硬盘上的程序。它们驻留在你的计算机上收集关于你在因特网上所做的一切事情的信息,并且 web站点可以在任何时候读取到Cookies收集到的所有信息...2021-06-21
  • Docker核心原理之 Cgroup详解

    cgroup的内核通过hook钩子来实现管理进程资源,提供了一个统一的接口,从单个进程的资源控制到操作系统层面的虚拟卡的过渡,今天通过本文给大家介绍Docker核心原理之 Cgroup详解,需要的朋友参考下吧...2021-07-07
  • 详解编译器编译原理

    这篇文章主要介绍了详解编译器编译原理的相关资料,需要的朋友可以参考下...2020-04-25
  • 在Visual Studio 中使用git及Git概念

    Git是一个开源的分布式版本控制系统,可以有效、高速的处理从很小到非常大的项目版本管理,是目前使用范围最广的版本管理工具,本文重点给大家介绍在Visual Studio 中使用git及git的工作原理,感兴趣的朋友一起看看吧...2021-05-19
  • JavaScript 详解预编译原理

    这篇文章主要介绍了JavaScript 详解预编译原理的相关资料,需要的朋友可以参考下...2017-01-26
  • C指针原理教程之语法树及其实现

    本文给大家分享的是如何使用C语言的指针原来来实现语法树,并给大家提供了详细的实例代码,希望大家能够喜欢...2020-04-25
  • 一文彻底弄懂零拷贝原理以及java实现

    零拷贝(英语: Zero-copy) 技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域,下面这篇文章主要给大家介绍了关于零拷贝原理以及java实现的相关资料,需要的朋友可以参考下...2021-08-13
  • 浅谈MySQL之浅入深出页原理

    首先,我们需要知道,页(Pages)是InnoDB中管理数据的最小单元。Buffer Pool中存的就是一页一页的数据。当我们要查询的数据不在Buffer Pool中时,InnoDB会将记录所在的页整个加载到Buffer Pool中去;同样,将Buffer Pool中的脏页刷入磁盘时,也是按照页为单位刷入磁盘的...2021-06-24
  • C语言kmp算法简单示例和实现原理探究

    这篇文章主要介绍了C语言kmp算法简单示例和实现原理探究,本文用简洁的语言说明KMP算法的原理,并给出了示例,需要的朋友可以参考下...2020-04-25