Node.js 中 require 与 import/export 的区别
require 是 CommonJS 动态加载(运行时同步求值),import/export 是 ES 模块静态声明(编译期分析、支持 tree-shaking)。Node 12+ 原生支持 ESM:package.json 设 "type":"module" 或用 .mjs 后缀。新项目推荐 ESM。
require/module.exports 属于 CommonJS(Node 的传统模块系统),import/export 属于 ECMAScript 标准模块(ESM)。 核心区别:require 是运行时的函数调用、可写在任何位置、同步加载;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) |
|---|---|---|
| 加载时机 | 运行时动态 | 编译期静态 |
| 可否条件加载 | 可以(if 里 require) | 静态不行,用动态 import() |
| 加载方式 | 同步 | 异步 |
| tree-shaking | 不支持 | 支持 |
| 浏览器原生 | 不支持 | 支持 |
| 顶层 await | 不支持 | 支持 |
如何启用 ESM
// package.json
{ "type": "module" }
或者文件直接用 .mjs 后缀;CommonJS 文件对应 .cjs。
互操作
- ESM 里可以
import cjs from './old.cjs'(拿到的是 module.exports 作为 default); - CJS 里不能
require()ESM 模块(Node 22+ 的部分版本开始实验性支持 require(esm)),通用做法是用动态const m = await import('./m.mjs')。
常见问题(FAQ)
Q:老项目要迁移吗?
不必强迁。大量生产代码仍是 CJS 且运行良好。新包、新服务用 ESM;库作者可考虑双格式发布(bundler 会用 ESM 入口做 tree-shaking)。
Q:ESM 里 __dirname 没了怎么办?
用 import.meta.url 换算:const __dirname = path.dirname(fileURLToPath(import.meta.url));,Node 20.11+ 可直接 import.meta.dirname。
Q:为什么 ESM 里引入本地文件必须写全后缀?
ESM 遵循浏览器规则,路径必须完整:import './util.js' 而不是 ./util。CommonJS 的自动补全在 ESM 中不适用。