包管理
包管理工具对比
结论先行:新项目一律用 pnpm。追求极致启动速度且项目无存量包袱可试 bun。
npm够用但磁盘效率差,yarn classic已被时代淘汰,yarn berry的 PnP 学习成本高、兼容性有坑。
| 维度 | npm | yarn classic | yarn berry (v2+) | pnpm(推荐) | bun |
|---|---|---|---|---|---|
| 安装速度 | 慢 | 中 | 中 | 快(硬链接 + 并行) | 极快(原生 Zig) |
| 磁盘占用 | 高(每个项目独立副本) | 高 | 中 | 极低(全局 store) | 中 |
| 锁文件 | package-lock.json | yarn.lock | yarn.lock | pnpm-lock.yaml | bun.lockb(二进制) |
| Monorepo | 支持(弱) | 不支持 | 支持 | 原生支持,体验最佳 | 支持(较新) |
| 幽灵依赖 | 存在 | 存在 | 可关闭 | 默认隔离 | 存在 |
| Node 兼容 | ✅ | ✅ | ✅ | ✅ | 部分兼容 |
| 生态成熟度 | 最成熟 | 维护中 | 较成熟 | 成熟 | 发展中 |
| 学习成本 | 零 | 低 | 高(PnP 概念) | 低 | 低 |
| 适用场景 | 老项目维护 | 历史项目 | 特殊需求 | 新项目首选 | 新项目、追求启动速度 |
关键概念:
- 幽灵依赖(Phantom Dependency):能
import到但没在package.json声明的包。npm/yarn classic因为把依赖平铺到node_modules根目录,导致子依赖能被「偷」到用,看似方便实则升级时一删就炸。pnpm 通过软链接 + 严格隔离彻底解决。 - 硬链接 / 全局 store:pnpm 把所有包下载到
~/.pnpm-store,项目里只放硬链接。同一台机器装 100 个项目,磁盘只占 1 份。 - PnP(Plug'n'Play):yarn berry 砍掉
node_modules,包解析靠.pnp.cjs映射表,理论速度最快但很多工具(webpack、jest、各种 loader)不兼容,需要.pnp.cjs兜底,不推荐作为默认选项。
面试话术:
「我们项目用 pnpm,核心三点:一是硬链接全局 store 节省磁盘,CI 上百个 job 拉同一个 store;二是非平铺的
node_modules杜绝幽灵依赖,package.json 删哪个就真没哪个;三是原生 workspace 体验比 npm/yarn 都顺。」
一、实践指南
- 必须 commit lock 文件:
package-lock.json/pnpm-lock.yaml/yarn.lock - CI 用
--frozen-lockfile:保证 lock 不被修改 - 依赖要定期更新:
pnpm outdated+ Renovate 自动化 - 不要装大而全的包:选按需引入的(如
lodash-es而非lodash) - 循环依赖要警惕:a 依赖 b、b 依赖 a,几乎一定意味着设计有问题
- 私有包用私有 registry:配置
.npmrc切到公司内网 - 避免
latest/*:锁版本才能保证构建可复现
二、依赖类型选择
| 字段 | 装在什么环境 | 例子 |
|---|---|---|
dependencies | 生产 + 开发 | react、lodash、axios |
devDependencies | 仅开发 | typescript、eslint、webpack |
peerDependencies | 由宿主提供 | react(类库要 react)、antd 的 peer |
optionalDependencies | 装失败也不报错 | fsevents(仅 macOS) |
原则:能在
devDependencies就别放dependencies,能peer就别dependencies。
三、版本号规范(SemVer)
major.minor.patch
- major:破坏性变更
- minor:新增功能(向后兼容)
- patch:bugfix
锁定方式:
{
"dependencies": {
"react": "18.2.0", // 精确
"lodash": "^4.17.21", // 兼容 4.x.x
"antd": "~4.20.0", // 兼容 4.20.x
"dayjs": "latest" // 不推荐
}
}
推荐用
^配合package-lock.json锁定精确版本,构建可复现。
四、Monorepo 实践
// 根 package.json
{
"name": "my-monorepo",
"private": true,
"workspaces": ["packages/*"]
}
monorepo/
├─ packages/
│ ├─ ui/ # 组件库
│ ├─ utils/ # 工具库
│ └─ app/ # 主应用
├─ pnpm-workspace.yaml
└─ package.json
pnpm-workspace.yaml:
packages:
- "packages/*"
子包之间引用:
// packages/app/package.json
{
"dependencies": {
"@my/ui": "workspace:*",
"@my/utils": "workspace:*"
}
}
常用工具:pnpm + Turborepo 或 pnpm + nx。详见 Monorepo 实战。
五、常用 script 模板
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint . --ext .ts,.tsx --fix",
"lint:check": "eslint . --ext .ts,.tsx",
"format": "prettier --write \"src/**/*.{ts,tsx,less,md}\"",
"test": "vitest",
"test:coverage": "vitest run --coverage",
"typecheck": "tsc --noEmit",
"prepare": "husky"
}
}
六、常见命令速查
按需查阅,不用记。
# npm
npm init # 初始化
npm i <pkg> # 安装到 dependencies
npm i -D <pkg> # 安装到 devDependencies
npm i -g <pkg> # 全局安装
npm i <pkg>@<version> # 安装指定版本
npm uninstall <pkg> # 卸载
npm run <script> # 运行脚本
npm list --depth=0 # 查看已安装包
npm outdated # 检查过期依赖
# pnpm(推荐)
pnpm add <pkg> # 默认装到 dependencies
pnpm add -D <pkg> # devDependencies
pnpm add -g <pkg> # 全局
pnpm add -w <pkg> # workspace 根
pnpm filter <pkg> <cmd> # 对子包执行命令
七、package.json 字段详解
字典式参考,按字段名检索。
下面列出常见字段,更详细的内容参考 npm 官方文档
{
// 包的唯一标识,npm view <包名> 查看是否被占用
"name": "",
// 遵循语义化版本规范 SemVer
"version": "",
/******* 描述信息 *******/
"description": "",
"keywords": [],
"author": "",
"contributors": [],
"homepage": "",
"bugs": { "url": "" },
"repository": { "type": "git", "url": "" },
/******* 脚本配置 *******/
"scripts": {
"start": "",
"build": "",
"test": ""
},
// 配置脚本中使用的环境变量,通过 process.env.npm_package_config_xxx 获取
"config": { "port": "8080" },
/******* 依赖配置 *******/
"dependencies": {},
"devDependencies": {},
"peerDependencies": {},
"optionalDependencies": {},
"bundledDependencies": [],
"engines": {},
/******* 文件 *******/
"main": "", // 入口
"browser": "", // 浏览器环境入口
"module": "", // ESM 入口
"bin": { "jupiter": "" }, // 可执行文件
"files": [], // publish 时包含的文件
"directories": {},
/******* 发布配置 *******/
"private": false,
"publishConfig": { "registry": "" },
"preferGlobal": true,
"os": [],
"cpu": [],
"license": "",
/******* 第三方配置 *******/
"typings": "types/index.d.ts",
"eslintConfig": {},
"lint-staged": {},
"gitHooks": { "pre-commit": "lint-staged" },
"babel": {},
"browserslist": {
"production": [">0.2%", "not dead", "not op_mini all"],
"development": ["last 1 chrome version"]
}
}