为何module.exports=require(...)直接赋值可行,经临时变量赋值却报错?
CommonJS与ES模块互操作的特殊赋值机制问题
问题场景
项目结构:
. ├── dep │ ├── a.mjs │ ├── b.js │ └── c.js └── entry.mjs
代码内容:entry.mjs:
import { foo } from "./dep/a.mjs"; console.log(foo);
dep/a.mjs:
export * from './b.js'
dep/b.js(有效写法):
module.exports = require("./c.js"); // 💯
dep/b.js(无效写法):
// 为何此写法不生效 ❌ // const m = require("./c.js"); // module.exports = m;
dep/c.js:
exports.foo = "foo";
运行node entry.mjs时,有效写法能正常输出foo,无效写法会抛出解构错误。
原因解析
这是Node.js处理ES模块与CommonJS模块互操作时的特殊逻辑:
- 当CommonJS模块直接执行
module.exports = require(...)时,Node.js会识别出这是将另一个模块的导出对象直接赋值给当前模块的导出,此时在ES模块用export * from导入该CommonJS模块时,Node.js会自动展开目标模块的导出成员,让ES模块能正确解构出foo这类具体导出。 - 而通过临时变量中转时,Node.js无法识别这个赋值的来源是另一个模块的导出,会将当前CommonJS模块的
module.exports视为普通对象,ES模块的export * from无法从普通CommonJS模块对象中解析出命名导出,因此解构foo时会报错。
相关文档参考
Node.js官方文档中关于ES模块与CommonJS互操作的章节明确说明:ES模块导入CommonJS模块时,默认会将其视为只有默认导出的模块,但针对export * from处理CommonJS模块有特殊兼容逻辑——仅当CommonJS模块的module.exports直接引用另一个模块的导出(即module.exports = require(...)形式)时,才能被ES模块识别并展开命名导出。
Vue3源码中的应用
Vue3源码中大量采用这种写法,是为了在混合使用ES模块和CommonJS模块的场景下,确保跨模块的导出能在Node.js环境或各类构建工具中被正确解析,避免互操作时的导出丢失问题。
内容的提问来源于stack exchange,提问作者bigboss
相关产品推荐
相关产品推荐

