node.js 里 ESM 与 CommonJS 的区别


可能大部分同学并不会直接用 node.js 开发 Web 后端程序,但是作为现代化前端,我们日常的各种开发都严重依赖开发脚手架,也即 node.js 环境下的各种工具链。目前已经有一些仓库逐步迁移到 ESM,所以了解一下 node.js 里 ESM 和 CommonJS 的区别也很有必要。

我还是老习惯,后文列举出的差异并非文档记录,而是我在实操中遇到的大坑小坑,希望记下来能节省将来的时间和大家的时间。

0. 设计原则

我们可以把 CommonJS 理解成按需加载,需要什么就加载什么,加载进来就执行。所以可以动态加载、条件加载、循环加载。

ESM 倾向于静态加载,方便解析依赖,优化运行效率。所以起初不能条件加载或者循环加载。不过后面考虑到实际需求,还是开放了 import() 做动态加载。

这个设计原则可以导出后面的诸多不同。

1. package.json

package.json 里添加 type: 'module' 可以开启本项目的 ESM。不写或者 type: 'commonjs' 则继续使用 CommonJS。

如果没有此配置,虽然我们代码中写的 好像是 ESM,但其实都会被 Babel 或其它什么工具转译成 CommonJS,最终运行的并不是 ESM。这点一定要注意。

2. __dirname

ESM 不再支持用 __dirname__filename 获取正在执行的 JS 文件在系统中的路径。作为替代方案,可以使用 import.meta.url 获取当前文件的 URL,不过返回结果是 file:// 协议,如果要继续使用 __dirname 可以这样:

import {dirname} from 'path';
import { fileURLToPath } from 'url';

function getDirname(url) {
  const __filename = fileURLToPath(url);
  return dirname(__filename);
}

3. 解构

使用 CommonJS 时,我们可以直接对导出的对象进行解构,比如:

// lib.js
module.exports = {
  foo: 'bar',
};

// index.js
const {foo} = require('./lib');

这样的用法在 ESM 中不可行。ESM 解构只能针对使用 export foo = 'bar' 这样主动暴露出的属性。对于一般对象的解构,我们只能写成:

// lib.js
export default {
  foo: 'bar',
}

// index.js
import lib from './lib';

const {foo} = lib;

4. .mjs 文件与 .cjs 文件

我们知道,node.js 加载模块时可以省去文件扩展名,比如 require('./foo'),不需要写最后的 .js.json,node.js 会自动去目录里查找对应的文件。

node.js 会根据 type 的不同使用不同默认策略加载 js,我们也可以使用特定扩展名要求 node.js 在加载时使用特定模块类型。比如,我们使用 ESM 时,node.js 会把加载进来的 JS 都当 ESM 处理,如果这些 JS 还在使用 CommonJS 加载其它 JS,就会报错(前面说了,ESM 里不支持 require)。此时,我们可以把目标文件的扩展名写为 .cjs,node.js 就会当它是 CommonJS 来处理了。

此功能在使用第三方库的时候很有用。比如 Postcss,它会加载项目里的配置文件,但它只支持 CommonJS,这时,如果执行时因没有 require 报错,就可以把配置文件的扩展名改成 .cjs

5. 顶层 await

开启 ESM 之后,可以使用顶层 await,省去一个异步函数。

(这是促使我使用 ESM 的主要原因)

6. importrequire

ESM 中,我们可以使用 import 导入 CommonJS 模块和 ESM 模块;但是 CommonJS 的 require 只能用来导入 CommonJS 模块。如果要在 CommonJS 中导入 ESM 模块,需要使用 import() 然后异步处理。

自然,ESM 里不能用 require

7. 其它区别

这些区别我在实际开发中没有遇到,大家自己阅读吧:Modules: ECMAScript modules | Node.js v17.8.0 Documentation (nodejs.org)


参考阅读:


《“node.js 里 ESM 与 CommonJS 的区别”》 有 2 条评论

欢迎吐槽,共同进步

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

%d 博主赞过: