别再纠结了!Node.js 新手选模块方案:require 还是 import?一文帮你做决定
在 Node.js 开发中,最让新手(甚至老手)头疼的问题之一就是:到底该用 require (CommonJS) 还是 import (ESM)?
尤其是在写一些自动化脚本、小型爬虫或者个人博客后端这种“普通小项目”时,这种纠结感尤为强烈。如果你去搜教程,有的教你 const express = require('express'),有的教你 import express from 'express'。
今天我们不谈太深奥的底层原理,直接从实战效率和未来趋势的角度,给你的小项目一个明确的参考答案。
一、 先给结论:普通小项目,我建议优先选 import (ESM)
如果你的 Node.js 环境在 v14.13.0 以上(现在一般都 v18 或 v20 了),新项目请无脑选择 ESM (import)。
为什么?
- 浏览器标准对齐:JavaScript 的官方模块标准是 ESM。你在前端框架(Vue/React)里写的是
import,后端也写import,能保持思维的高度统一。 - 支持顶级 await (Top-level await):这是 ESM 的杀手锏。在
import模式下,你可以直接在文件顶层写const data = await fetch(...),而不需要包裹在一个异步匿名函数(async () => { ... })()里。这对于写小工具、脚本非常爽。 - 生态趋势:越来越多的顶级开源库(如
node-fetch,chalk,del)在最新版本中已经彻底放弃了 CommonJS,只支持 ESM。如果你坚持用require,你会被迫停留在这些库的旧版本上。
二、 如何在 Node.js 小项目里开启 import?
默认情况下,Node.js 认为 .js 文件是 CommonJS。要切换到 ESM,最简单的方法只需要一步:
在你的项目根目录执行 npm init -y 后,打开 package.json,添加一行:
{
"name": "my-small-project",
"type": "module", // <--- 关键是这一行
"dependencies": { ... }
}
添加这一行后,该目录下所有的 .js 文件都会被视为 ESM,你就可以愉快地使用 import 了。
三、 避坑指南:切到 ESM 后最常见的 3 个“坑”
很多同学从 require 转到 import 会报错,通常是因为以下三个原因:
1. 变量 __dirname 和 __filename 消失了
在 CommonJS 里,这两个变量随处可用。但在 ESM 里,它们不存在。
解决方法:手动通过 import.meta.url 获取。
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
2. 引入本地文件必须带后缀
在 require 里,你可以写 require('./tools')。但在 ESM 中,你必须写全路径:import { myFunc } from './tools.js'(注意那个 .js 不能省)。
3. 引入 JSON 文件变麻烦了
以前直接 const data = require('./config.json')。现在 ESM 下,官方标准的 JSON 导入还在实验阶段,建议这样写:
import { readFileSync } from 'fs';
const config = JSON.parse(readFileSync(new URL('./config.json', import.meta.url)));
四、 什么时候该退守使用 require?
虽然我极力推荐 import,但在以下几种情况,用 require 反而更简单:
- 极简的单文件脚本:如果你只想写一个不到 50 行的
.js脚本,且不想创建package.json,那么直接用require最快,不需要任何配置。 - 维护老旧项目:如果项目里已经有成百上千个
require,没必要为了“新潮”去大面积重构。 - 对启动速度极度敏感:在某些极端场景下,CommonJS 的同步加载可能比 ESM 稍快那么一点点(但在普通小项目中可以忽略不计)。
五、 核心差异一览表
| 特性 | CommonJS (require) |
ESM (import) |
|---|---|---|
| 官方状态 | 事实上的旧标准 | ECMAScript 官方标准 |
| 加载方式 | 运行时同步加载 | 编译时静态加载(支持异步) |
| 顶级 await | 不支持 | 支持 |
| 默认注入 | __dirname, module 等 |
无(需自行实现) |
| 浏览器兼容 | 不支持(需 Webpack 等工具) | 原生支持 |
总结
对于现在的开发者来说,import 是投资未来,require 是守望过去。
在你的下一个 Node.js 小项目里,尝试在 package.json 中加入 "type": "module",习惯 import 的节奏。你会发现,这种与前端无缝衔接的编程体验,真的非常丝滑。