1、Node.js与npm/yarn/pnpm:前端工程化的基石
说实话,前端工程化这件事,绕不开的第一个坎就是 Node.js。我记得刚入行那会儿,还在用 jQuery 写页面,打包全靠手动合并 JS 文件。后来第一次接触 Node.js,感觉就像打开了新世界的大门——原来前端也能有自己的运行时环境。
Node.js 运行时环境安装与版本管理
Node.js 本质上是一个基于 Chrome V8 引擎的 JavaScript 运行时。说白了,就是让你能在服务器端跑 JS 代码。安装方式很简单,去官网下载安装包就行。但这里有个坑——不同项目依赖的 Node 版本可能不一样。
我曾经接手过一个老项目,它只能跑在 Node 10 上,而我本地装的是 Node 18。一运行就报错,折腾了半天才发现是版本问题。从那以后,我就养成了一个习惯:用 nvm 管理 Node 版本。
nvm(Node Version Manager) 是 Mac/Linux 下的版本管理工具。Windows 用户可以用 nvm-windows。
安装 nvm 后,常用的命令就这几个:
# 查看可安装的版本
nvm ls-remote
# 安装指定版本
nvm install 18.16.0
# 切换版本
nvm use 18.16.0
# 设置默认版本
nvm alias default 18.16.0
嗯,这里要注意:nvm 和 nvm-windows 的命令略有不同。Windows 下用 nvm list 查看已安装版本,Mac 下用 nvm ls。别搞混了。
npm/yarn/pnpm 包管理器对比与选型
Node.js 装好之后,npm 就自动带上了。但市面上还有 yarn 和 pnpm,它们到底有什么区别?我个人的经验是这样的:
| 特性 | npm | yarn | pnpm |
|---|---|---|---|
| 安装速度 | 较慢 | 快 | 最快 |
| 磁盘占用 | 高(每个项目都复制一份) | 高 | 低(硬链接共享) |
| 依赖隔离 | 扁平化(容易冲突) | 扁平化 | 严格隔离(更安全) |
| lock 文件 | package-lock.json | yarn.lock | pnpm-lock.yaml |
为什么会这样?pnpm 的核心优势在于它用硬链接的方式存储依赖。你想想看,如果 10 个项目都用 lodash,npm 会复制 10 份,pnpm 只存一份,其他项目通过硬链接引用。这在磁盘空间上省太多了。
我个人建议:新项目直接用 pnpm。老项目如果已经用了 yarn,也没必要强行迁移。npm 嘛...说实话,除了自带的便利性,现在优势不大了。
package.json 核心字段解析
每个 Node.js 项目根目录下都有个 package.json,它就像项目的身份证。我见过不少新手把这里面的字段搞混,尤其是 dependencies 和 devDependencies。
{
"name": "my-project", // 项目名
"version": "1.0.0", // 版本号
"description": "一个示例项目", // 描述
"main": "index.js", // 入口文件
"scripts": { // 脚本命令
"dev": "vite",
"build": "vite build"
},
"dependencies": { // 运行时依赖
"vue": "^3.3.0"
},
"devDependencies": { // 开发依赖
"vite": "^4.0.0",
"typescript": "^5.0.0"
}
}
小技巧:dependencies 是生产环境也要用的,比如 Vue、React。devDependencies 是开发时才需要的,比如构建工具、测试框架。别把 vite 装到 dependencies 里,否则部署到服务器上会多出一堆没用的包。
还有一个容易被忽略的字段是 peerDependencies。我曾经写过一个插件,依赖 Vue 2,但用户的项目用的是 Vue 3。如果不声明 peerDependencies,npm 会同时安装两个版本的 Vue,直接导致运行时崩溃。
node_modules 与依赖管理机制
node_modules 这个文件夹,可以说是前端开发者的噩梦。随便一个项目,node_modules 可能就有几百兆。为什么会这么大?
npm 早期用的是嵌套结构。比如 A 依赖 B,B 又依赖 C,那 node_modules 里就是 A/node_modules/B/node_modules/C。这种结构的问题很明显——路径太深,而且同一个包可能被安装多次。
后来 npm 改成了扁平化结构。所有依赖都尽量放在顶层的 node_modules 里。但这样又带来了新问题:依赖冲突。比如项目依赖 A@1.0 和 B@1.0,而 A 和 B 都依赖 C,但版本要求不同。这时候 npm 只能把其中一个 C 放到子目录里。
避坑指南:我曾经遇到过一个问题,项目本地跑得好好的,部署到 CI 上就报错。查了半天,发现是本地 node_modules 里有个幽灵依赖——某个包没有在 package.json 里声明,但因为扁平化结构被提升到了顶层,代码里直接引用了它。CI 上重新安装后,这个包就不在了。所以,永远不要直接引用未声明的依赖。
pnpm 的解决方案就优雅多了。它用硬链接把包存在全局 store 里,node_modules 里只放软链接。这样既节省空间,又保证了依赖隔离——每个项目只能访问自己 package.json 里声明的包。
npx 与全局工具调用
npx 是 npm 5.2 之后自带的一个工具。它的作用很简单:执行 node_modules 里的命令,或者临时下载一个包来执行。
举个例子,你想用 create-react-app 创建一个项目,但又不想全局安装它:
npx create-react-app my-app
这条命令会临时下载 create-react-app,执行完后自动删除。既省了全局安装的麻烦,又不用担心版本污染。
我个人习惯用 npx 来运行项目里的脚本。比如团队里有人用 yarn,有人用 npm,那在文档里写 npx eslint 就比 ./node_modules/.bin/eslint 更通用。
总结一下:Node.js 是前端工程化的根基,nvm 帮你管理版本,pnpm 帮你管理依赖,npx 帮你临时调用工具。这四个东西配合好了,开发体验能提升一大截。
嗯,这一章的内容就到这里。下一章我们会聊聊模块化规范——CommonJS 和 ES Module 到底怎么选?为什么说 ESM 是未来?到时候见。