使用差异
Commonjs 与 ESM 差异
- CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
- CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
- CommonJS 模块的
require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
差异一
CommonJS 模块输出的是一个值的拷贝
// a.js
let counter = 1
exports.counter = 1
counter++
// b.js
const { counter } = require('./a.js')
console.log(counter) // 输出 1
ES6 模块输出的是值的引用
// a.js
export let counter = 1
counter++
// b.js
import {counter} from './a.js'
console.log(counter) // 输出 2
差异二
CommonJs 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而ES6模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
差异三
ESM 异步加载模块
-
找到程序的入口文件
- 在html中
<script src='main.js' type='module'> - 在node中
package.json中通过"main": "./main.js"
- 在html中
-
根据入口文件
main.js的导入语句分析入口文件的依赖模块
- 然后再分析
counter.js的依赖,就这样一层一层的遍历,找出依赖并加载
- 如果浏览器主线程等待这些文件一个个都下载完成,那许多其它任务将堆积在主线程队列中
- 像这样阻塞主线程会使使用模块的应用程序使用速度太慢。所以ESM规范将算法拆分成三个阶段

-
构建--查找、下载所有文件并将其解析为模块记录(Module Record)并生成模块映射(Module Map)

-
实例化--在内存中找到内存地址(Memory Location)来存放所有导出的值,然后让导出和导入都指向该内存地址,这称为链接
-
评估--运行代码以使用变量的实际值填充
CommonJS 同步加载
因为CommonJS是从文件系统加载文件,这比通过Internet下载花费的时间少得多。这意味着Node可以在加载文件时阻塞主线程。同时也意味着在返回模块实例之前,你要遍历整个树,加载、实例化和评估任何依赖项。

Node.js的模块加载方法
-
采用ESM的情况
.mjs后缀的js文件内可以使用export和importpackage.json中指定type为module
-
采用CommonJS的情况
.cjs后缀的js文件内可以使用exports和requirepackage.json中指定type为commonjs
注意,ES6 模块与 CommonJS 模块尽量不要混用。require命令不能加载.mjs文件,会报错,只有import命令才可以加载.mjs文件。反过来,.mjs文件里面也不能使用require命令,必须使用import。
package.json的main字段
// ./node_modules/A/package.json
{
"type": "module",
"main": "./src/index.js"
}
上面代码指定项目的入口脚本为./src/index.js,它的格式为 ES6 模块。如果没有type字段,index.js就会被解释为 CommonJS 模块。
然后,其它项目就可以通过import命令就可以加载这个模块。
import { something } from 'A'
上面代码中,运行该脚本以后,Node.js 就会到./node_modules目录下面,寻找A模块,然后根据该模块package.json的main字段去执行入口文件。
package.json的exports字段
main的别名
exports字段的别名如果是., 就代表模块的主入口,优先级高于main字段,并且可以直接简写成exports字段
{
"exports": {
".": "./main.js"
}
// 等同于
"exports": "./main.js"
}
子目录别名
package.json文件的exports字段可以指定脚本或子目录的别名
// ./node_modules/es-module-package/package.json
{
"exports": {
"./submodule": "./src/submodule.js"
}
}
上面的代码指定src/submodule.js别名为submodule,然后就可以从别名加载这个文件。
import submodule from 'es-module-package/submodule';
// 加载 ./node_modules/es-module-package/src/submodule.js