直接赋值exports与Object.assign(exports)的导出差异原因解析
为什么CommonJS中Object.assign添加的属性无法被ES模块作为命名导出识别?
核心原因
Node.js在实现CommonJS(CJS)与ES模块(ESM)的兼容时,仅会通过静态扫描模块代码的方式,识别直接赋值给exports或module.exports的属性作为命名导出;而通过Object.assign、动态属性赋值(如exports['b'] = xxx)这类运行时动态添加的属性,不会被静态分析捕获,因此无法作为命名导出被ESM识别。
详细机制解析
模块系统的本质差异
ESM是静态模块系统,依赖预解析阶段确定所有导出和导入;而CJS是动态模块系统,允许在运行时动态修改导出对象。Node.js为了让两者互通,设计了一套兼容层,但这套兼容层无法完全抹平两者的差异。CJS转ESM命名导出的静态分析逻辑
当Node.js加载一个CJS模块供ESM导入时,会在模块执行前先做一次静态代码扫描:- 仅识别形如
exports.xxx = ...或module.exports.xxx = ...的直接赋值语句; - 将这些语句中的
xxx记录为该模块的命名导出项。
这个过程不执行模块代码,所以任何运行时才会执行的动态属性添加操作,都不会被纳入命名导出的列表。
- 仅识别形如
测试场景对应解释
在你的测试代码中:exports.a = 67是直接赋值语句,静态分析阶段被捕获,因此a成为可被ESM识别的命名导出;Object.assign(exports, { b: 68 })是运行时执行的动态操作,静态扫描无法预知它会给exports添加b属性,所以b不会被加入命名导出列表。
而import * as module1中看到的default导出,对应CJS模块的完整exports对象(运行时生成),所以b会出现在default里,但无法作为顶级命名导出被解构导入。
补充验证
如果将Object.assign替换为动态属性赋值exports['b'] = 68,同样无法被ESM作为命名导出识别,因为静态分析也无法识别这种动态键的赋值操作。
内容的提问来源于stack exchange,提问作者s100
相关产品推荐
相关产品推荐

