Node.js 基础:从安装到模块规范

说实话,很多前端同学刚开始接触 Node.js 时,都觉得它挺神秘的。其实说白了,Node.js 就是一个能让你用 JavaScript 写后端代码的运行环境。我当年第一次用 Node.js 搭开发服务器时,那种「原来前端也能干后端的事」的感觉,至今记忆犹新。

Node.js 安装与版本管理

安装 Node.js 看似简单,但这里有个坑——版本管理。你想想看,不同项目可能依赖不同版本的 Node.js,总不能每次都卸载重装吧?

推荐方案:使用 nvm(Node Version Manager)

我个人习惯用 nvm 来管理 Node.js 版本。它就像个版本切换器,随时可以切到你想要的版本。

# 安装 nvm(macOS/Linux)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash

# 安装指定版本
nvm install 18.16.0

# 切换版本
nvm use 18.16.0

# 查看已安装版本
nvm ls
小技巧: 我建议在项目根目录创建一个 .nvmrc 文件,写上你需要的 Node.js 版本号。这样团队成员 clone 项目后,直接运行 nvm use 就能自动切换。

Windows 用户可以用 nvm-windows,用法基本一致。嗯,这里要注意:安装完 nvm 后记得重启终端,否则可能找不到命令。

npm / yarn / pnpm 包管理器

包管理器是 Node.js 生态的基石。我见过不少新手直接 npm install 一把梭,结果 node_modules 里塞满了用不上的包。

包管理器 特点 适用场景
npm 官方自带,生态最全 个人项目、小团队
yarn 速度快,有离线缓存 需要稳定锁文件的团队
pnpm 磁盘空间节省,依赖隔离好 大型 monorepo 项目

npm 常用命令

# 初始化项目
npm init -y

# 安装依赖
npm install express

# 安装开发依赖
npm install --save-dev typescript

# 全局安装
npm install -g create-react-app
避坑指南: 我曾经在项目中直接 npm install 某个包的最新版本,结果第二天那个包发布了破坏性更新,整个项目跑不起来了。从那以后,我养成了锁定版本号的习惯——用 package-lock.json 或 yarn.lock 来锁定依赖版本。

pnpm 是我最近比较推崇的工具。它用硬链接的方式共享依赖,同一个包在磁盘上只存一份。你想想看,如果电脑上有 10 个项目都用 React,pnpm 只会存一份,而不是 10 份。这在磁盘空间紧张时特别有用。

# 安装 pnpm
npm install -g pnpm

# 使用 pnpm
pnpm install
pnpm add lodash
pnpm remove lodash

CommonJS 与 ES Module 规范

这是 Node.js 里最容易让人困惑的地方。说白了,就是两种不同的模块组织方式。

CommonJS(CJS)

Node.js 默认使用的模块规范。用 require 导入,module.exports 导出。

// 导出
// math.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;

module.exports = { add, subtract };

// 导入
// app.js
const math = require('./math.js');
console.log(math.add(2, 3)); // 5

ES Module(ESM)

JavaScript 官方标准,用 import/export 语法。Node.js 从 12 版本开始支持,但需要做一些配置。

// 导出
// math.mjs
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;

// 导入
// app.mjs
import { add, subtract } from './math.mjs';
console.log(add(2, 3)); // 5
核心区别:
  • CommonJS 是动态加载,运行时确定模块关系
  • ES Module 是静态加载,编译时就能确定依赖关系
  • CommonJS 支持条件导入(if 语句里 require),ESM 不行
  • ESM 支持 Tree Shaking,CommonJS 不支持

我在项目中遇到过这样一个问题:一个库同时提供了 CJS 和 ESM 两种版本,但我的项目是 CJS 的,结果 import 了一个 ESM 的包,直接报错了。为什么会这样?因为 Node.js 不允许在 CJS 中同步 import ESM 模块。

如何选择?

我个人建议:新项目直接用 ES Module。浏览器和 Node.js 都在往这个方向走。老项目如果已经用了 CommonJS,也没必要强行迁移,除非你有 Tree Shaking 的需求。

// package.json 中配置
{
  "type": "module"  // 整个项目使用 ESM
  // 或者不配置,默认 CJS
}

// 也可以在文件级别指定
// .mjs 文件强制使用 ESM
// .cjs 文件强制使用 CJS
实用技巧: 如果你在 ESM 项目中需要引入 CJS 模块,可以用 createRequire 来创建 require 函数。我在迁移一个老项目时就用过这个办法,避免了大规模重写。
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const lodash = require('lodash');

嗯,关于模块规范,还有一点要提醒:循环依赖。CommonJS 在处理循环依赖时,会返回未完成的导出对象。ESM 则通过实时绑定解决了这个问题。如果你在项目中遇到了奇怪的 undefined 错误,不妨检查一下是不是有循环依赖。

最后说一句:工具是死的,人是活的。不管用 npm 还是 pnpm,CJS 还是 ESM,关键是理解它们背后的设计思想。这样遇到问题时,你才能快速定位并解决。