动态 import()

发布日期 · 标签:ECMAScript ES2020

动态 import() 引入了一种新的类似函数的 import 形式,与静态 import 相比,它解锁了新的功能。本文比较了这两种形式,并概述了新功能。

静态 import (回顾) #

Chrome 61 版本开始支持 ES2015 import 语句,该语句位于 模块 中。

考虑以下模块,位于 ./utils.mjs

// Default export
export default () => {
console.log('Hi from the default export!');
};

// Named export `doStuff`
export const doStuff = () => {
console.log('Doing stuff…');
};

以下是静态导入和使用 ./utils.mjs 模块的方法

<script type="module">
import * as module from './utils.mjs';
module.default();
// → logs 'Hi from the default export!'
module.doStuff();
// → logs 'Doing stuff…'
</script>

注意: 上面的示例使用 .mjs 扩展名来表示它是一个模块,而不是一个常规脚本。在 Web 上,文件扩展名并不重要,只要文件以正确的 MIME 类型(例如,JavaScript 文件的 text/javascript)在 Content-Type HTTP 标头中提供即可。

.mjs 扩展名在其他平台(如 Node.jsd8)上特别有用,因为这些平台没有 MIME 类型或其他强制性钩子(如 type="module")来确定某个东西是模块还是常规脚本。为了跨平台一致性,并明确区分模块和常规脚本,我们在这里使用相同的扩展名。

这种导入模块的语法形式是静态声明:它只接受字符串字面量作为模块标识符,并通过运行时前的“链接”过程将绑定引入本地范围。静态 import 语法只能在文件的顶层使用。

静态 import 使得静态分析、捆绑工具和树摇动等重要用例成为可能。

在某些情况下,以下操作很有用:

  • 按需(或有条件地)导入模块
  • 在运行时计算模块标识符
  • 从常规脚本(而不是模块)中导入模块

静态 import 无法实现这些操作。

动态 import() 🔥 #

动态 import() 引入了一种新的类似函数的 import 形式,它满足了这些用例。import(moduleSpecifier) 返回对请求模块的模块命名空间对象的承诺,该对象是在获取、实例化和评估所有模块的依赖项以及模块本身后创建的。

以下是动态导入和使用 ./utils.mjs 模块的方法

<script type="module">
const moduleSpecifier = './utils.mjs';
import(moduleSpecifier)
.then((module) => {
module.default();
// → logs 'Hi from the default export!'
module.doStuff();
// → logs 'Doing stuff…'
});
</script>

由于 import() 返回一个承诺,因此可以使用 async/await 代替基于 then 的回调样式

<script type="module">
(async () => {
const moduleSpecifier = './utils.mjs';
const module = await import(moduleSpecifier)
module.default();
// → logs 'Hi from the default export!'
module.doStuff();
// → logs 'Doing stuff…'
})();
</script>

注意: 虽然 import() 看起来像函数调用,但它被指定为语法,只是碰巧使用了括号(类似于 super())。这意味着 import 不会从 Function.prototype 继承,因此你无法对其进行 callapply 操作,并且 const importAlias = import 之类的事情也不起作用——实际上,import 甚至不是一个对象!不过,在实践中这并不重要。

以下是如何在小型单页应用程序中使用动态 import() 实现导航时按需加载模块的示例

<!DOCTYPE html>
<meta charset="utf-8">
<title>My library</title>
<nav>
<a href="books.html" data-entry-module="books">Books</a>
<a href="movies.html" data-entry-module="movies">Movies</a>
<a href="video-games.html" data-entry-module="video-games">Video Games</a>
</nav>
<main>This is a placeholder for the content that will be loaded on-demand.</main>
<script>
const main = document.querySelector('main');
const links = document.querySelectorAll('nav > a');
for (const link of links) {
link.addEventListener('click', async (event) => {
event.preventDefault();
try {
const module = await import(`/${link.dataset.entryModule}.mjs`);
// The module exports a function named `loadPageInto`.
module.loadPageInto(main);
} catch (error) {
main.textContent = error.message;
}
});
}
</script>

动态 import() 实现的按需加载功能在正确应用时非常强大。为了演示目的,Addy 修改了 一个 Hacker News PWA 示例,该示例在首次加载时静态地导入所有依赖项,包括评论。更新后的版本 使用动态 import() 按需加载评论,避免在用户真正需要它们之前加载、解析和编译的成本。

注意: 如果你的应用程序从另一个域(静态或动态)导入脚本,则这些脚本需要以有效的 CORS 标头(如 Access-Control-Allow-Origin: *)返回。这是因为与常规脚本不同,模块脚本(及其导入)使用 CORS 获取。

建议 #

静态 import 和动态 import() 都很有用。它们都有各自非常不同的用例。对于初始绘制依赖项(尤其是对于首屏内容)使用静态 import。在其他情况下,考虑使用动态 import() 按需加载依赖项。

动态 import() 支持 #