先记住这个答案
不能。ESM 要求一个模块里的导出名唯一,不管是两次 export const a,还是 export { a } 写了两遍,都会报 SyntaxError。这个检查发生在解析阶段,所以模块里任何代码都不会执行,导入它的模块也跟着加载失败。要分清两个概念:导出名必须唯一,但同一个本地变量可以用多个不同的导出名暴露出去。
- 导出名重复在解析阶段就报
SyntaxError - 同一个变量可以用多个名字导出
- 默认导出就是名叫
default的导出,只能有一个 export *名字冲突不报错,而是两个都丢掉
哪些写法会报错
下面每一组都是一个独立的模块文件,报错信息来自 Node.js。
// 1. 声明时就重名:本地变量都重复了
export let x = 1
export function x() {} // SyntaxError: Identifier 'x' has already been declared
// 2. 导出列表写了两遍
const y = 1
export { y }
export { y } // SyntaxError: Duplicate export of 'y'
// 3. 两个默认导出
const z = 1
export { z as default }
export default 2 // SyntaxError: Duplicate export of 'default'第一种其实是本地变量重复声明,let 不允许同一作用域重名,和导出无关。第二、三种才是导出名重复。第三种说明 export default 和 export { z as default } 是一回事,都在注册一个名叫 default 的导出。
哪些写法看起来重名但没问题
同一个变量可以导出多次,只要导出名不同。比如 export { format, format as formatDate },外面用哪个名字导入都行,两个名字指向同一个变量。库在重命名 API 时常这样做,旧名字保留一段时间兼容老代码。
默认导出和具名导出也可以指向同一个东西。export default format 加上 export { format },导出名一个是 default、一个是 format,不冲突。很多库同时提供两种导入方式就是这么写的。
重新导出时的冲突
export { a } from './x.js' 这种显式转发,和本模块自己的 a 撞了一样会报 SyntaxError。
export * 比较特殊。index.js 里写了 export * from './a.js' 和 export * from './b.js',两个模块都导出了 version,这时不会报错,version 会被悄悄排除,两个都不导出。有人 import { version } from './index.js' 才报 SyntaxError: The requested module contains conflicting star exports for name 'version',而用 import * as ns 拿到的命名空间对象上根本没有 version。如果 index.js 自己显式导出了 version,那就以显式导出为准,不存在冲突。
容易答错的地方
- 以为后一个导出会覆盖前一个
- 有些人按对象属性的思路想,觉得后写的覆盖先写的。模块导出不是对象赋值,是静态声明,重复就是语法错误,没有覆盖这回事。
- 以为默认导出和同名具名导出会冲突
export default function format() {}和export { format }能共存吗?能。默认导出的导出名是default,不是format。函数名format只是本地变量名。- 以为报错时模块已经执行了一部分
- 语法错误在解析阶段就发现了,模块代码一行都不执行,依赖它的整个模块图也不会执行。不会出现执行了一半的中间状态。
面试官还会怎么问?
TypeScript 能在编码时就发现吗?
能。TypeScript 编译器会报重复标识符或重复导出的错误,编辑器里直接标红。打包工具在解析阶段也会报错,所以这类问题一般到不了线上。
桶文件里 export * 冲突了怎么排查?
冲突的名字会悄悄消失,症状是某个导出「明明有却导入不到」。可以用 import * as ns 打印 Object.keys(ns) 看少了哪个,再逐个检查被转发的模块。修法是在桶文件里用 export { version } from './a.js' 显式指定要哪一个。
CommonJS 里重复给 exports.a 赋值会报错吗?
不会。exports 就是一个普通对象,后赋的值覆盖先赋的,没有任何提示。这也是 ESM 静态导出的好处之一,重名问题在解析阶段就暴露了。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。