动态 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.js 和 d8
)上特别有用,因为这些平台没有 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
继承,因此你无法对其进行 call
或 apply
操作,并且 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()
支持 #
- Chrome: 从版本 63 开始支持
- Firefox: 从版本 67 开始支持
- Safari: 从版本 11.1 开始支持
- Node.js: 从版本 13.2 开始支持
- Babel: 支持