本节摘要:npm 是 Node 的官方包管理器,也是全球最大软件仓库的入口。本节覆盖高频命令与依赖类型、node_modules 扁平化结构的由来与幽灵依赖问题、npm/yarn/pnpm 的设计差异,以及 scripts 与 npx 的日常用法。目标:装得快、装得对、装得可复现。
npm init -y # 生成 package.json npm install # 按清单安装全部依赖 npm install express --save # 装包并记入 dependencies(默认 --save) npm install -D jest # 记入 devDependencies npm install -g pm2 # 全局安装(通常是命令行工具) npm uninstall express # 卸载并从清单移除 npm outdated # 查看过期依赖 npm audit # 安全审计已知漏洞 npm ci # 严格按 lockfile 安装,CI 专用
最容易混淆的是 install 与 ci:前者会做版本仲裁、可能改写 lockfile;后者要求 lockfile 与清单完全一致,按锁定版本原样安装、装完删掉旧 node_modules,速度更快也更可复现——CI 流水线里永远用 ci。
五种依赖类型各管一段生命周期:
| 类型 | 去向 | 典型内容 |
|---|---|---|
| dependencies | 生产环境 | express、数据库驱动 |
| devDependencies | 仅开发与测试 | jest、eslint |
| peerDependencies | 宿主提供 | 插件声明自己兼容的框架版本 |
| optionalDependencies | 可选 | 装不上也不报错的功能增强 |
| bundledDependencies | 打包随行 | 需要一起发布的私有模块 |
peerDependencies 值得多看一眼:写插件时声明它,可以避免同一个框架被装两份——两份 React 或两份 Express 混用是诡异的运行时错误来源。
npm v2 时代,每个依赖把自己的依赖装进自己的 node_modules,树越套越深,同样的 lodash 在不同分支重复出现,一个中型项目轻松几百 MB。npm v3 起改为扁平化(提升):尽量把所有包拍平到顶层 node_modules,只有版本冲突时才下沉嵌套。
这带来一个著名的副作用——幽灵依赖:你在 package.json 里只声明了 express,但代码里 require('debug') 也能成功,因为它是 express 的依赖、被提升到了顶层。问题在于这层便利没有契约保障:哪天 express 升级换掉了 debug,你的代码就在没有改任何一行的情况下崩了。
治理手段有三个层次:
1. 规约层:lint 规则禁止 require 未声明的包(如 eslint-plugin-import) 2. 工具层:改用 pnpm,默认只暴露已声明的依赖 3. 审查层:定期 npm ls <包名> 查看某包从哪条依赖链来
npm ls 是排查「版本打架」的第一工具,它会打印出依赖树里同名的不同版本及其引入路径。
yarn 在 npm v6 时代以离线缓存与确定性安装胜出,如今功能差距已小。真正的思路革新来自 pnpm:
代价是「太严格」:从 npm 迁过来的老项目里那些幽灵依赖会立刻暴露为报错——这其实是帮你把隐患提前引爆。我的建议:新项目直接 pnpm,老项目迁移前先跑一遍 lint 把幽灵依赖清干净。
package.json 的 scripts 字段是项目的自动化入口:
{ "scripts": { "start": "node app.js", "dev": "node --watch app.js", "test": "jest --coverage", "lint": "eslint ." } }
npm run dev 会把项目内 node_modules 的 bin 目录加入临时 PATH,所以本地安装的命令行工具不需要全局装。npx 则更进一步——直接执行,未安装就临时拉取:
npx cowsay "依赖不该为了用一次而常驻" npx eslint --fix src
这也回答了「全局安装何时合适」:只有跨项目天天用的通用工具(如第 7 章的 pm2)值得 -g,其余尽量本地化,把环境装进项目而不是装进机器——新同事克隆仓库后一条 install 即可复现你的全部工具链。
⚠️ 常见坑:install 失败时先看网络与 registry 镜像配置,再怀疑代码;删 node_modules 重装是最后一招,别当成第一反应。锁文件冲突时以「合并工具的 union 策略 + 重新 install 校验」解决,而不是手改 lockfile。
当项目多到需要共享代码时,下一步是私有 registry 与 workspace:前者让公司内部包不经过公网分发,后者让单仓库多包共享依赖与脚本。这两件事都不急于第一时间做,但值得知道路在哪里——依赖治理的尽头不是工具,而是团队对「可复现构建」的共识。