Node.js 简介

CommonJS 与 ES Module 模块化

掌握 Node.js 中 CommonJS 和 ES Module 两种模块化方案的写法、区别与选择建议。

🎯 引言

学完这篇文章,你能理解为什么代码要拆分成模块,会用 CommonJS 和 ES Module 两种方式导出、导入代码,并清楚它们的区别,知道新项目该怎么选。


🧱 为什么需要模块化

刚学编程时,我们习惯把所有代码写在一个文件里。代码一多问题就来了:变量重名互相覆盖、找不到某个函数写在哪、改一处崩一片。

模块化就是把代码按功能拆到不同文件里,每个文件只管自己的事,需要谁就把它导入进来。 比如 utils.js 专门放工具函数,user.js 专门处理用户数据,app.js 负责把它们组织起来。

Node.js 历史上有两套模块化方案同时存在:传统的 CommonJS 和官方的 ES Module,两套都要看得懂。


🧱 CommonJS:Node.js 的传统方案

CommonJS 是 Node.js 诞生时就有的方案,用 module.exports 导出、require 导入。

utils.js
function add(a, b) {
    return a + b;
}

const user = {
    name: '小明',
    age: 18,
};

// 导出给其他文件使用
module.exports = {
    add,
    user,
};
app.js
// 导入自己的文件,要写相对路径
const { add, user } = require('./utils.js');

console.log(add(1, 2)); // 3
console.log(user.name); // 小明

如果只想导出单个值,也可以直接赋值:module.exports = add。另外 exports.add = addmodule.exports 的简写形式,两者指向同一个对象。

不能直接写 exports = add,这会切断 exportsmodule.exports 的关联,导致导出失败。单个导出请用 module.exports = add

🧱 ES Module:官方标准方案

ES Module(简称 ESM)是 JavaScript 语言的官方模块化标准,你在 Vite 项目里写的 import / export 就是它。Node.js 中使用 ESM 有两种开启方式:

  • 方式一:把文件后缀改成 .mjs
  • 方式二(推荐):在 package.json 中加上 "type": "module",之后项目里的 .js 文件都按 ESM 处理。
package.json
{
    "name": "my-app",
    "type": "module"
}
utils.js
export function add(a, b) {
    return a + b;
}

export const user = {
    name: '小明',
    age: 18,
};
app.js
import { add, user } from './utils.js';

console.log(add(1, 2)); // 3
console.log(user.name); // 小明
ESM 中导入本地文件必须写完整后缀 .js,写 import { add } from './utils' 会直接报错找不到模块。这和 Vite 项目里的习惯不一样,是初学者的高频踩坑点。

⚡ 两种方案的区别

对比项CommonJSES Module
导出 / 导入module.exports / requireexport / import
加载方式运行时同步加载编译期静态分析,异步加载
开启方式默认就是.mjs 后缀或 "type": "module"
本地文件后缀可省略 .js必须写 .js
__dirname可以直接用不可用,需用 import.meta.dirname
互相引用不能 require ESM 模块可以 import CommonJS 模块
导入内置模块时建议加 node: 前缀,例如 require('node:fs')import fs from 'node:fs'。一眼就能看出这是 Node.js 内置模块,而不是 npm 上同名的第三方包。

💡 新手该怎么选

新项目直接用 ES Module。 它和前端 Vite、Vue 项目里的写法完全一致,一套语法前后端通用,也是官方明确的发展方向。

老项目遇到 CommonJS 能看懂即可。 大量存量项目和 npm 包还在用 CommonJS,维护时不需要强行改写。


🧾 小节总结

  • 模块化把代码按功能拆到不同文件,解决命名冲突和依赖混乱问题。
  • CommonJS 用 module.exports 导出、require 导入,是 Node.js 的传统默认方案。
  • ES Module 用 export / import,通过 .mjs 后缀或 "type": "module" 开启。
  • ESM 导入本地文件必须写完整 .js 后缀,且没有 __dirname
  • 新项目建议用 ES Module,和前端工程化写法保持一致。

❓ 知识问答

Q1:module.exportsexports 有什么区别?

exportsmodule.exports 的引用简写,给它加属性(exports.add = add)等价于给 module.exports 加属性。但直接重新赋值 exports = xxx 会失效,因为模块最终导出的是 module.exports

Q2:为什么 ESM 里用 __dirname 会报错?

__dirname 是 CommonJS 包装出来的变量,ESM 的设计里没有它。Node.js 20.11 之后可以直接用 import.meta.dirname 代替,后面的 path 模块一篇会详细讲。

Q3:两种方案能在同一个项目里混用吗?

可以,但有规则:ESM 可以 import CommonJS 模块,CommonJS 不能 require ESM 模块。混用容易把自己绕晕,建议一个项目统一用一种。

Q4:.mjs"type": "module" 选哪个?

做项目用 "type": "module" 更方便,所有 .js 文件统一按 ESM 处理。.mjs 适合写单个独立脚本,不依赖 package.json 配置。


🧪 小练习

用 ES Module 方式实现一个计算器模块:

  1. 创建 package.json,加入 "type": "module"
  2. 创建 calc.js,导出 add(加法)和 multiply(乘法)两个函数。
  3. 创建 app.js,导入这两个函数,分别计算 add(2, 3)multiply(4, 5) 并打印。
calc.js
// 请在这里编写代码
app.js
// 请在这里编写代码

🎉 恭喜你已经掌握 Node.js 的两种模块化方案!下一篇我们学习 fs 文件系统模块,让程序具备读写文件的能力。