Last updated on Oct 2, 2022

ESM 和 CJS 的导入和导出方式

(没有讨论 dynamic import 这种情况)

ESM

// 导出方式
// named export
export { export1 }
// default export
export default defaultExport
// export from
export { export1 } from 'module-name'

// 导入方式
// named import
import { export1 } from 'module-name'
// default import
import defaultExport from 'module-name'
// namespace import
import * as name from 'module-name'
// side effect import
import 'module-name'

CJS

/ / 导出方式
// lib.cjs
module.exports = {
  a: 1,
  b: 2
}
// 和上面等价,算一种
exports.a = 1
exports.b = 2

// 导入
// main.cjs
const lib = require('./lib')
console.log('a:', lib.a)
console.log('b:', lib.b)

问题来了,ESM 和 CJS 如何进行互操呢?

  1. ESM 导入 CJS
  2. CJS 导入 ESM
  3. (不讨论)dynamic import 导入 ESM
  4. (不讨论)dynamic import 导入 CJS

复杂度:1. ESM 多种导入和导出方式;2. 不同平台的处理方式不同

不同平台:(包含不限于)Rollup、Webpack、Babel、TypeScript、浏览器、Node.js

分析

ESM 处理与 CJS 的交互性问题

ESM 推出之前,CJS 已经被广泛使用。为了解决 ESM 和 CJS 的互操问题,ESM 中 default 的导出名称被赋予了特殊的语法。

export default defaultExport

// 不需要写
import { default as foo } from 'bar'
// 而是直接写
import foo from 'bar'

对照

const a = 1
// 导出导入方式一
export { a }

import { a as foo } from 'bar'
console.log(foo) // 1
// 导出导入方式二
export default a

import foo from 'bar'
console.log(foo) // 1

所以当 CJS 导入 ESM 时,可以使用新的 ESM 语法来实现兼容:(例:lib.cjs 导入 main.mjs

// lib.cjs
module.exports = {
  a: 1,
  b: 2,
}
// main.mjs
import lib from '../lib.cjs'
console.log(lib) // { a: 1, b: 2 }
// main.cjs
const lib = require('./lib.cjs')
console.log(lib) // { a: 1, b: 2 }

可以看到 CJS 导入 ESM 的行为和 CJS 导入 CJS 的行为一致。(即 import foo from 'bar' 等价于 const foo = require('bar')

如何理解 ESM 中 default 的导出名称被赋予了特殊的语法?