如何动态导入ECMAScript模块
ECMAScript(又称ES2015,或ES)模块是一种组织JavaScript中具有凝聚力的代码块的方式。
ES模块系统有两个角色:
- 导入模块--使用模块的人
import { func } from './myModule.js' - 导入模块--导出
export const func = () => {},并被导入的模块。
导入模块使用import 语法来导入一个依赖关系。
javascript
// The importing module
import { concat } from './concatModule.js';
concat('a', 'b'); // => 'ab'
而导入的模块使用export 语法导出其组件:
javascript
// The imported module exports components
export const concat = (paramA, paramB) => paramA + paramB;
import { concat } from './concatModule.js' 使用ES模块的方式是静态的:意味着模块之间的依赖关系在编译时就已经知道。静态依赖总是包含在应用程序的包中。
静态导入在大多数情况下是有效的。但有时你想节省一点客户端的带宽,动态地加载模块。
如果你把import 作为一个函数--import(pathToModule) ,你就可以动态导入模块,这个功能从ES2020开始提供。
让我们来看看动态导入是如何工作的,以及什么时候它是有用的。
1.模块的动态导入
当import 关键字作为一个函数使用时:
javascript
const module = await import(path);
它返回一个承诺并启动一个异步任务来加载模块。如果模块被成功加载,那么承诺会解析到模块内容,否则,承诺会拒绝。
path 可以是任何表达式,其值为表示路径的字符串。有效的路径表达式有:
javascript
// Classic string literals
const module1 = await import('./myModule.js');
// A variable
const path = './myOtherModule.js';
const module2 = await import(path);
// Function call
const getPath = (version) => `./myModule/versions/${version}.js`;
const moduleVersion1 = await import(getPath('v1.0'));
const moduleVersion2 = await import(getPath('v2.0'));
因为import(path) 返回一个承诺,所以它与async/await 语法非常匹配。例如,让我们在一个异步函数中加载一个模块。
javascript
async function loadMyModule() {
const myModule = await import('./myModule.js');
// ... use myModule
}
loadMyModule();
现在,知道了如何加载模块,让我们从导入的模块中提取组件(默认或命名)。
2.导入组件
2.1 导入命名出口
让我们考虑下面这个模块,命名为namedConcat.js 。
javascript
// namedConcat.js
export const concat = (paramA, paramB) => paramA + paramB;
namedConcat 执行concat 函数的命名导出。
如果你想动态地导入namedConcat.js ,并访问命名的导出concat ,那么只需通过命名的导出对解析的模块对象进行结构化。
javascript
async function loadMyModule() {
const { concat } = await import('./namedConcat.js');
concat('b', 'c'); // => 'bc'
}
loadMyModule();
2.2默认导出的导入
要动态地导入一个缺省,只需从模块对象中读取default 属性。
假设defaultConcat.js 将函数作为default 的导出:
javascript
// defaultConcat.js
export default (paramA, paramB) => paramA + paramB;
当动态导入defaultConcat.js ,特别是访问default 导出时,你需要的仅仅是读取default 属性。
但是有一个细微的差别。default 是JavaScript中的一个关键字,所以它不能被用作变量名。你要做的是使用去结构化与别名:
javascript
async function loadMyModule() {
const { default: defaultFunc } = await import('./defaultConcat.js');
defaultFunc('b', 'c'); // => 'bc'
}
loadMyModule();
2.3 导入混合内容
如果导入的模块导出了default 和多个命名的导出,那么你可以使用单一的析构访问所有这些组件。
javascript
async function loadMyModule() {
const {
default: defaultImport,
namedExport1,
namedExport2
} = await import('./mixedExportModule.js');
// ...
}
loadMyModule();
3.何时使用动态导入
我建议在有条件地导入大模块时使用动态导入。
例如,你可能会根据运行时的条件,不时地使用该模块。或者你可能想加载一个大模块的不同版本,这也取决于运行时间条件。
javascript
async function execBigModule(condition) {
if (condition) {
const { funcA } = await import('./bigModuleA.js');
funcA();
} else {
const { funcB } = await import('./bigModuleB.js');
funcB();
}
}
execBigModule(true);
对于只有几十行代码的小模块(如前面例子中的namedConcat.js 或defaultConcat.js ),动态导入并不值得麻烦。
4.总结
要动态加载一个模块,可以把import(path) 作为一个函数来调用,其参数是模块的指定符(又称路径)。
const module = await import(path) 返回一个承诺,该承诺可解析为一个包含导入模块组件的对象。
在该对象中,default 属性包含默认的出口,而命名的出口则包含在相应的属性中。
javascript
const {
default: defaultComponent,
namedExport1,
namedExport2
} = await import(path);
Node.js(13.2及以上版本)和大多数现代浏览器都支持动态导入。