Node.js 中 require 与 import/export 的区别

require 是 CommonJS 动态加载(运行时同步求值),import/export 是 ES 模块静态声明(编译期分析、支持 tree-shaking)。Node 12+ 原生支持 ESM:package.json 设 "type":"module" 或用 .mjs 后缀。新项目推荐 ESM。

最佳实践
Node.js 事件循环与异步任务示意

require/module.exports 属于 CommonJS(Node 的传统模块系统),import/export 属于 ECMAScript 标准模块(ESM)。 核心区别:require 是运行时的函数调用、可写在任何位置、同步加载;静态 import 声明必须在顶层,支持静态分析;动态 import() 可在表达式中使用,tree-shaking 由构建工具实施。Node 12+ 已原生支持 ESM,新项目一律用 ESM。

语法对照

// CommonJS
const fs = require('fs');
module.exports = myModule;

// ESM
import fs from 'node:fs';
export default myModule;
export const helper = () => {};

关键差异

维度 CommonJS (require) ESM (import)
加载时机 require 调用时 静态建立依赖图,模块代码仍在运行时执行
可否条件加载 可以(if 里 require) 静态不行,用动态 import()
加载方式 同步 异步
tree-shaking 动态模式通常更难分析 有利于构建工具分析,Node 不自动删去未用代码
浏览器原生 不支持 支持
顶层 await 不支持 支持

如何启用 ESM

// package.json
{ "type": "module" }

或者文件直接用 .mjs 后缀;CommonJS 文件对应 .cjs。

互操作

  • ESM 里可以 import cjs from './old.cjs'(拿到的是 module.exports 作为 default);
  • 现代 Node 的部分受支持版本可 require 同步 ESM(包括依赖图不含顶层 await 等限制),按目标版本检查;通用做法是在 async 函数内使用 const m = await import('./m.mjs')。从 CJS 检测到的具名导出并不保证覆盖所有动态写法。

常见问题(FAQ)

Q:老项目要迁移吗?

不必强迁。大量生产代码仍是 CJS 且运行良好。新包、新服务用 ESM;库作者可考虑双格式发布(bundler 会用 ESM 入口做 tree-shaking)。

Q:ESM 里 __dirname 没了怎么办?

用 import.meta.url 换算:const __dirname = path.dirname(fileURLToPath(import.meta.url));,支持该属性的 Node 版本可用 import.meta.dirname,仅适用于 file: 模块;状态与可用性应按目标版本核对。

Q:为什么 ESM 里引入本地文件必须写全后缀?

ESM 遵循浏览器规则,本地相对/绝对文件路径必须完整(裸包说明符受 exports 等规则控制):import './util.js' 而不是 ./util。CommonJS 的自动补全在 ESM 中不适用。

参考资料

获取专属方案

联系我们

加入社区

微信扫码
加入官方交流群

立即体验

在线开通,按量计费,真正的云服务!

立即开始

选择观测云版本

代码托管平台