CommonJS和ESM(ECMAScript Modules)是两种不同的模块化规范。

CommonJS是Node.js最初采用的模块化规范,它使用require函数来引入模块,使用module.exportsexports来导出模块。CommonJS模块是同步加载的,也就是说,当一个模块被引入时,它会立即执行模块中的代码,并将module.exportsexports对象作为模块的输出。

ESM是ES6引入的模块化规范,它使用import语句来引入模块,使用export语句来导出模块。ESM模块是异步加载的,也就是说,当一个模块被引入时,它不会立即执行模块中的代码,而是等到所有依赖的模块都加载完成后再执行。

CommonJS 规范

  1. 支持引入自己编写的模块
  2. 支持引入第三方模块
  3. 支持引入node内置模块
  4. 支持引入json文件
  5. 支持引入addon c++扩展模块, 一般用不到,需要借助node-gyp编译成二进制.node文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// commonjs规范
// 1. 引入自己的模块
const export = require('./export.js');
console.log(export) // export.js
// 2. 引入第三方模块
const md5 = require('md5');
console.log(md5('20231025')); // 060ff7b9fd1472a4db724d43d8c991bb
// 3. 引入node内置模块
const fs = require('fs');
console.log(fs);
// 4. 引入json文件
const data = require('./data.json');
console.log(data);
// 5. 支持引入addon c++扩展模块, 一般用不到,需要借助node-gyp编译成二进制.node文件
const nodeModule = require('./nodeModule.node'); // 假装引入nodeModule.node模块

导出模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
name: 'nodejs',
age: 8,
}
// module.exports可以理解为是一个对象,可以通过.的方式添加属性
// exports.xxx = value 可以理解为是对module.exports的引用,不能直接覆盖
// exports = module.exports = value 可以直接覆盖
// exports = value 不能直接覆盖,因为exports是module.exports的引用,相当于给exports重新赋值,不会影响module.exports的值
console.log(module.exports === exports); // true
console.log(exports); // {}
console.log(module.exports); // {}
exports.name = 'nodejs';

// 导入
const obj = require('./export')
console.log(obj); // { name: 'nodejs', age: 8, fn: [Function: fn] }
const { name, age, fn } = require('./export')
console.log(name, age); // nodejs 8 [Function: fn]

ESM规范

需要将package.jsontype改为module

导出

1
2
3
4
5
6
7
8
// 每个模块只能有一个默认导出
export default {
name: 'default export'
}

export const name = 'nodejs';
export const age = 8;
export const fn = () => 'module export'

导入

1
2
3
4
5
6
7
8
import * as obj from './export.js';
console.log(obj);
//[Module: null prototype] {
// age: 8,
// default: { name: 'default export' },
// fn: [Function: fn],
// name: 'nodejs'
//}

导入的目标文件中有重复变量名时,可在导入时重命名

1
2
3
4
5
6
// export.js 
export const name = 'nodejs';
// main.js
let name = 'esm import'
import {name as exportName} from './export.js'
console.log(exportName) // nodejs

ESM无法导入json文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import data from './data.json'
console.log(data);

TypeError [ERR_IMPORT_ASSERTION_TYPE_MISSING]: Module "file:///Users/xiuji/Desktop/workplace/own/Node/data.json" needs an import assertion of type "json"
at new NodeError (node:internal/errors:372:5)
at validateAssertions (node:internal/modules/esm/assert:82:15)
at defaultLoad (node:internal/modules/esm/load:24:3)
at ESMLoader.load (node:internal/modules/esm/loader:407:26)
at ESMLoader.moduleProvider (node:internal/modules/esm/loader:326:22)
at new ModuleJob (node:internal/modules/esm/module_job:66:26)
at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:345:17)
at ESMLoader.getModuleJob (node:internal/modules/esm/loader:304:34)
at async ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:82:21)
at async Promise.all (index 0) {
code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING'
}
ELIFECYCLECommand failed with exit code 1.

CommonJS与ESM的区别

1、CommonJS是基于运行时的同步加载,ESM是基于编译时的异步加载

2、CommonJS可以修改导出的值,ESM不可以

3、CommonJS不可以tree-shaking,ESM可以tree-shaking,tree-shaking是指只打包用到的代码(按需加载)

4、CommonJS中顶层的this指向当前模块,ESM中顶层的this指向undefined,ESM默认是严格模式,严格模式下this指向undefined

ESM相比于CommonJS有以下优点:

  1. ESM支持静态分析,可以在编译时进行优化,而CommonJS只能在运行时进行优化。
  2. ESM支持异步加载,可以提高应用程序的性能。
  3. ESM支持循环依赖,而CommonJS不支持。

但是,ESM也有一些缺点:

  1. ESM的语法比较复杂,需要使用importexport语句来导入和导出模块。
  2. ESM的兼容性不如CommonJS,需要使用Babel等工具进行转换。
  3. ESM的生态系统相对较新,可能存在一些不稳定性和兼容性问题。