ESM模块如何实现类似CommonJS的程序化动态导出?
ESM的export是静态语法关键字,不是CommonJS里那个可读写的exports对象,你写的export[group] = xxx从语法层面就不合法。
ESM规范在模块解析阶段就会确定所有导出项,不允许在循环、条件判断等运行时逻辑里动态新增命名导出;且import * as xxx拿到的模块命名空间对象是密封不可修改的,从机制上就不存在和CJS完全一致的、运行时往导出列表加属性的能力。
方案1:默认导出聚合对象(改动成本最低)
直接把原本要挂到exports上的内容全部挂到自定义的普通对象上,最后默认导出这个对象即可,逻辑和你原来的CJS代码几乎一致:
const cloudFunctions = { // 你原有的函数集合定义 } const exportCollector: Record<string, any> = {} let group: keyof typeof cloudFunctions; // 逻辑和原来完全一致,只是操作自己定义的收集对象 for (group in cloudFunctions) { if (group != null) { exportCollector[group] = cloudFunctions[group]; } } export default exportCollector;
如果你的循环里没有额外的过滤、转换逻辑,甚至可以直接写export default cloudFunctions,连循环操作都不需要。
导入时使用:
// 先导入默认对象,再解构拿需要的函数 import funcSet from './your-module-path.js' const { funcA, funcB } = funcSet
这个方案适合不需要Tree Shaking、对导入写法无强制要求的场景,代码改动量最小。
方案2:构建阶段生成静态导出(适配ESM全生态)
如果你需要支持import { funcA } from 'xxx'这种命名导入写法、或者需要打包工具做Tree Shaking优化,可以在构建环节加一段简单的扫描逻辑,提前读取cloudFunctions的所有键,自动生成静态导出代码,效果和你手动逐个写导出完全一致:
// 构建阶段自动生成,不需要手动维护 export const funcA = cloudFunctions.funcA; export const funcB = cloudFunctions.funcB; // ... 其余所有函数对应的静态导出
这个方案没有任何运行时兼容问题,所有ESM运行环境、打包工具都能完美支持,是生产环境的推荐方案。
不推荐方案:运行时拼字符串构造动态模块
部分支持data URL导入的环境(比如Node.js)可以把动态导出逻辑拼成JS字符串,通过import()加载data URL生成临时模块实现动态命名导出,但这种方式性能差、调试困难,除非有强需求否则不建议使用。
内容的提问来源于stack exchange,提问作者Cupid Chan

