CommonJS 命名空间


CommonJS 模块由可以是任何类型的 module.exports 对象组成。

当导入 CommonJS 模块时,可以使用 ES 模块默认导入或其对应的语法糖可靠地导入:

import { default as cjs } from 'cjs';

// 下面的导入语句是上面的导入语句中
// `{ default as cjsSugar }` 的 "语法糖"(等价但更甜):
import cjsSugar from 'cjs';

console.log(cjs);
console.log(cjs === cjsSugar);
// 打印:
//   <module.exports>
//   true

CommonJS 模块的 ECMAScript 模块命名空间表示始终是使用 default 导出键指向 CommonJS module.exports 值的命名空间。

当使用 import * as m from 'cjs' 或动态导入时,可以直接观察到此模块命名空间外来对象:

import * as m from 'cjs';
console.log(m);
console.log(m === await import('cjs'));
// 打印:
//   [Module] { default: <module.exports> }
//   true

为了更好地兼容 JS 生态系统中的现有用法,Node.js 还尝试确定每个导入的 CommonJS 模块的 CommonJS 命名导出,以使用静态分析过程将它们作为单独的 ES 模块导出提供。

例如,考虑编写的 CommonJS 模块:

// cjs.cjs
exports.name = 'exported';

前面的模块支持 ES 模块中的命名导入:

import { name } from './cjs.cjs';
console.log(name);
// 打印: 'exported'

import cjs from './cjs.cjs';
console.log(cjs);
// 打印: { name: 'exported' }

import * as m from './cjs.cjs';
console.log(m);
// 打印: [Module] { default: { name: 'exported' }, name: 'exported' }

从上一个记录模块命名空间外来对象的示例中可以看出,name 导出是从 module.exports 对象复制出来的,并在导入模块时直接设置在 ES 模块命名空间上。

未检测到这些命名导出的实时绑定更新或添加到 module.exports 的新导出。

命名导出的检测基于通用语法模式,但并不总是正确地检测命名导出。 在这些情况下,使用上述默认导入形式可能是更好的选择。

命名导出检测涵盖了许多常见的导出模式、再导出模式、以及构建工具和转译器输出。 参阅 cjs-module-lexer 以了解实现的确切语义。

CommonJS modules consist of a module.exports object which can be of any type.

When importing a CommonJS module, it can be reliably imported using the ES module default import or its corresponding sugar syntax:

import { default as cjs } from 'cjs';

// The following import statement is "syntax sugar" (equivalent but sweeter)
// for `{ default as cjsSugar }` in the above import statement:
import cjsSugar from 'cjs';

console.log(cjs);
console.log(cjs === cjsSugar);
// Prints:
//   <module.exports>
//   true

The ECMAScript Module Namespace representation of a CommonJS module is always a namespace with a default export key pointing to the CommonJS module.exports value.

This Module Namespace Exotic Object can be directly observed either when using import * as m from 'cjs' or a dynamic import:

import * as m from 'cjs';
console.log(m);
console.log(m === await import('cjs'));
// Prints:
//   [Module] { default: <module.exports> }
//   true

For better compatibility with existing usage in the JS ecosystem, Node.js in addition attempts to determine the CommonJS named exports of every imported CommonJS module to provide them as separate ES module exports using a static analysis process.

For example, consider a CommonJS module written:

// cjs.cjs
exports.name = 'exported';

The preceding module supports named imports in ES modules:

import { name } from './cjs.cjs';
console.log(name);
// Prints: 'exported'

import cjs from './cjs.cjs';
console.log(cjs);
// Prints: { name: 'exported' }

import * as m from './cjs.cjs';
console.log(m);
// Prints: [Module] { default: { name: 'exported' }, name: 'exported' }

As can be seen from the last example of the Module Namespace Exotic Object being logged, the name export is copied off of the module.exports object and set directly on the ES module namespace when the module is imported.

Live binding updates or new exports added to module.exports are not detected for these named exports.

The detection of named exports is based on common syntax patterns but does not always correctly detect named exports. In these cases, using the default import form described above can be a better option.

Named exports detection covers many common export patterns, reexport patterns and build tool and transpiler outputs. See cjs-module-lexer for the exact semantics implemented.