动态导入
注意:动态导入需要 Meteor 1.5 或更高版本。
dynamic-import
包提供了 Module.prototype.dynamicImport
的实现,它是模块运行时的扩展,为 动态 import(...)
语句提供支持,这是 ECMAScript 标准中一个即将到来(ECMA2020)的补充。
动态 import(...)
语句是静态 import
技术(用于加载模块)的补充方法。静态 import
的模块会被捆绑到初始 JavaScript 包中,而动态 import()
的模块则在运行时从服务器获取。
动态从服务器获取模块后,它会在客户端永久缓存,并且对同一版本模块的后续请求不会产生往返服务器的请求。如果模块发生了更改,则始终会从服务器获取新的副本。
用法
import(...)
语句返回一个 Promise
,当模块成功从服务器获取并准备使用时,它会解析为模块的 exports
。
因为它是一个 Promise
,所以开发人员可以使用几种方法来决定在动态加载的模块可用时会发生什么
Promise
的 .then()
方法
import("tool").then((tool) => tool.task());
在异步函数中使用 await
Meteor 支持 async
和 await
,它们提供了一种简单的方法来异步等待模块准备就绪,而无需提供回调函数
async function performTask() {
const tool = await import("tool");
tool.task();
}
警告
默认导出
import(...)
Promise
解析为模块的 exports
。如果需要使用模块的“默认”导出,它将在结果对象的 default
属性上可用。在上面的示例中,这意味着它将作为 tool.default
可用。使用参数解构来提供额外的清晰度可能会有所帮助
import("another-tool").then(({ default: thatTool }) => thatTool.go());
使用带动态表达式的 import()
如果您尝试使用任何计算表达式进行导入,例如
let path = "example";
const module = await import(`/libs/${path}.js`);
您会收到类似这样的错误
Error: Cannot find module '/libs/example.js'
Meteor 的构建过程会构建一个所有使用静态分析导入或需要的所有文件的图。然后,它会创建引用的文件的精确捆绑包,并使它们可供客户端用于 import()
。
如果没有完整的导入语句(静态、动态或 require
),Meteor 不会使该模块可用于 import()
。
使动态表达式起作用的解决方案是创建一个“白名单”模块,该模块可以被构建过程读取,但实际上并不运行。例如
if (false) {
import("/libs/example.js");
import("/libs/another-example.js");
import("/libs/yet-another-example.js");
}
确保从客户端和服务器入口点都导入白名单。
与其他打包系统的区别
在 Meteor 的实现中,客户端拥有有关哪些模块在初始包中、哪些模块在本地缓存中以及哪些模块仍需要获取的完整信息。单个客户端发出的请求之间永远不会有任何重叠,服务器的响应中也不会出现任何不需要的模块。您可以将此策略称为精确代码拆分,以将其与打包区分开来。
此外,初始包包含所有可用动态模块的哈希值,因此客户端不必询问服务器是否可以使用模块的缓存版本,并且同一客户端永远不需要再次下载同一版本的模块。此缓存系统具有不可变缓存的所有优点。
Meteor 还允许动态表达式,只要依赖项在代码的其他地方静态表达即可。这之所以可能,是因为 Meteor 的客户端模块系统了解如何在运行时解析动态字符串(这在 webpack 或 browserify 中是不正确的,因为它们用数字替换模块标识符字符串)。但是,可用模块集受您(程序员)明确决定允许导入(直接或在白名单中)的字符串文字的约束。