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
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
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
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const lodash = require('lodash');
嗯,关于模块规范,还有一点要提醒:循环依赖。CommonJS 在处理循环依赖时,会返回未完成的导出对象。ESM 则通过实时绑定解决了这个问题。如果你在项目中遇到了奇怪的 undefined 错误,不妨检查一下是不是有循环依赖。
最后说一句:工具是死的,人是活的。不管用 npm 还是 pnpm,CJS 还是 ESM,关键是理解它们背后的设计思想。这样遇到问题时,你才能快速定位并解决。