CommonJS 和 ESM 如何互操作(互导)?有什么坑?
ESM 导入 CJS
// CJS 模块 (math.cjs)
module.exports = { add: (a, b) => a + b, PI: 3.14 }
// ESM 导入
import math from './math.cjs' // default 导入 = module.exports
console.log(math.add(1, 2)) // 3
// 命名导入(Node.js 支持,但有坑)
import { add } from './math.cjs' // 可能可以,取决于环境坑 1:命名导入不一定可用
CJS 的 module.exports 是运行时确定的,ESM 的 import {} 是编译时确定的。Node.js 通过静态分析尝试提供命名导入,但不保证成功:
// CJS 动态导出(ESM 无法静态分析)
if (condition) {
module.exports = { a: 1 }
} else {
module.exports = { b: 2 }
}
// ESM 中命名导入可能失败
import { a } from './dynamic.cjs' // 可能报错或 undefined安全做法:
import pkg from './math.cjs'
const { add, PI } = pkg // 运行时解构CJS 导入 ESM
CJS 不能用 require 加载 ESM(因为 ESM 是异步的),必须用 import():
// CJS 中加载 ESM
async function load() {
const mod = await import('./math.mjs')
console.log(mod.add(1, 2))
}
load()坑 2:default 导出差异
// ESM 模块
export default { add: (a, b) => a + b }
// CJS 中 import()
const mod = await import('./math.mjs')
console.log(mod.default.add(1, 2)) // 需要通过 .default
console.log(mod.add) // undefined(没有命名导出 add)坑 3:__dirname 和 __filename
ESM 中没有 __dirname 和 __filename:
// CJS
console.log(__dirname) // /path/to/dir
console.log(__filename) // /path/to/dir/file.js
// ESM 替代方案
import { fileURLToPath } from 'url'
import { dirname } from 'path'
const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)坑 4:JSON 导入
// CJS
const config = require('./config.json') // ✅ 直接可用
// ESM(需要 assert 语法)
import config from './config.json' with { type: 'json' }
// 或旧语法
import config from './config.json' assert { type: 'json' }
// 兼容方案:读取文件
import { readFileSync } from 'fs'
const config = JSON.parse(readFileSync('./config.json', 'utf-8'))坑 5:require.resolve
// CJS
const path = require.resolve('./module')
// ESM 需要使用 import.meta.resolve(实验性)
const path = import.meta.resolve('./module')双格式包(Dual Package)
同时提供 CJS 和 ESM 两种入口:
// package.json
{
"name": "my-lib",
"exports": {
"import": "./dist/index.mjs", // ESM 入口
"require": "./dist/index.cjs" // CJS 入口
}
}最佳实践
- 新项目一律使用 ESM
- 库项目提供双格式包
- ESM 中导入 CJS 用 default import + 解构
- 避免在 ESM 中依赖
require