包管理器
问题
npm、yarn、pnpm 有什么区别?package.json 中的依赖版本号如何理解?
答案
包管理器负责管理项目依赖。npm 是 Node.js 默认包管理器,yarn 和 pnpm 是更快更高效的替代方案。
npm
常用命令
# 初始化项目
npm init -y
# 安装依赖
npm install lodash # 生产依赖
npm install -D typescript # 开发依赖
npm install -g ts-node # 全局安装
# 更新与删除
npm update lodash
npm uninstall lodash
# 查看信息
npm list # 本地依赖树
npm outdated # 过期依赖
npm view lodash versions # 查看可用版本
# 发布
npm login
npm publish
package.json
{
"name": "my-app",
"version": "1.0.0",
"description": "My Application",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"dev": "ts-node src/index.ts",
"build": "tsc",
"test": "jest",
"lint": "eslint src/**/*.ts"
},
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"typescript": "^5.0.0",
"@types/express": "^4.17.17"
},
"peerDependencies": {
"react": ">=16.8.0"
},
"engines": {
"node": ">=18.0.0"
}
}
版本号规范(SemVer)
主版本.次版本.修订版本
MAJOR.MINOR.PATCH
| 符号 | 示例 | 含义 |
|---|---|---|
| 无符号 | 4.18.2 | 精确版本 |
^ | ^4.18.2 | 允许次版本和修订版本更新(4.x.x) |
~ | ~4.18.2 | 仅允许修订版本更新(4.18.x) |
* | * | 任何版本 |
>= | >=4.0.0 | 大于等于 |
x | 4.x | 通配符 |
{
"dependencies": {
"lodash": "^4.17.21", // 4.17.21 <= v < 5.0.0
"express": "~4.18.0", // 4.18.0 <= v < 4.19.0
"moment": "2.29.4" // 精确 2.29.4
}
}
yarn
常用命令
# 安装依赖
yarn add lodash
yarn add -D typescript
yarn global add ts-node
# 其他
yarn remove lodash
yarn upgrade lodash
yarn outdated
# 工作区
yarn workspaces list
Yarn Workspaces(Monorepo)
// 根目录 package.json
{
"name": "monorepo",
"private": true,
"workspaces": [
"packages/*"
]
}
monorepo/
├── package.json
├── packages/
│ ├── core/
│ │ └── package.json
│ ├── utils/
│ │ └── package.json
│ └── app/
│ └── package.json
pnpm
pnpm 使用硬链接和符号链接,节省磁盘空间,安装速度更快。
优势
| 特性 | npm | yarn | pnpm |
|---|---|---|---|
| 磁盘空间 | 每项目独立 | 每项目独立 | 共享存储 |
| 安装速度 | 慢 | 快 | 最快 |
| 幽灵依赖 | 存在 | 存在 | 不存在 |
| node_modules 结构 | 扁平 | 扁平 | 嵌套+符号链接 |
常用命令
# 安装
pnpm add lodash
pnpm add -D typescript
pnpm add -g ts-node
# 其他
pnpm remove lodash
pnpm update
pnpm outdated
# 查看存储
pnpm store status
pnpm store prune
pnpm node_modules 结构
node_modules/
├── .pnpm/ # 真实依赖存储
│ ├── lodash@4.17.21/
│ │ └── node_modules/
│ │ └── lodash/ # 硬链接到全局存储
│ └── express@4.18.2/
│ └── node_modules/
│ ├── express/
│ └── body-parser/ # 符号链接
├── lodash -> .pnpm/lodash@4.17.21/node_modules/lodash
└── express -> .pnpm/express@4.18.2/node_modules/express
幽灵依赖问题
// 幽灵依赖:使用了未在 package.json 中声明的包
// npm/yarn 扁平化导致可以访问间接依赖
// package.json 只声明了 express
// 但由于扁平化,可以 import express 的依赖
import bodyParser from 'body-parser'; // 危险!
// pnpm 严格模式下会报错
// 只有直接依赖才能访问
Lock 文件
| 包管理器 | Lock 文件 |
|---|---|
| npm | package-lock.json |
| yarn | yarn.lock |
| pnpm | pnpm-lock.yaml |
最佳实践
- Lock 文件应该提交到版本控制
- 团队使用相同的包管理器
- CI/CD 使用
npm ci/yarn install --frozen-lockfile/pnpm install --frozen-lockfile
常见面试问题
Q1: npm install 和 npm ci 有什么区别?
答案:
| 特性 | npm install | npm ci |
|---|---|---|
| 场景 | 开发环境 | CI/CD 环境 |
| package-lock.json | 可能更新 | 必须存在,不更新 |
| node_modules | 增量安装 | 先删除再安装 |
| 速度 | 较慢 | 更快 |
| 一致性 | 可能不一致 | 保证一致 |
# CI/CD 推荐
npm ci # npm
yarn install --frozen-lockfile # yarn
pnpm install --frozen-lockfile # pnpm
Q2: dependencies 和 devDependencies 有什么区别?
答案:
| 类型 | 作用 | 示例 |
|---|---|---|
| dependencies | 生产环境需要 | express, lodash |
| devDependencies | 仅开发时需要 | typescript, jest |
| peerDependencies | 宿主环境提供 | react(插件) |
| optionalDependencies | 可选依赖 | fsevents |
{
"dependencies": {
"express": "^4.18.0" // 生产代码使用
},
"devDependencies": {
"typescript": "^5.0.0", // 开发时编译
"jest": "^29.0.0" // 测试
},
"peerDependencies": {
"react": ">=16.8.0" // React 组件库
}
}
# 生产环境只安装 dependencies
npm install --production
Q3: pnpm 为什么比 npm/yarn 快?
答案:
- 硬链接:所有包存储在全局 store,项目通过硬链接引用
- 符号链接:依赖关系通过符号链接组织
- 并行下载:更高效的并行下载策略
- 避免重复:相同版本只下载一次
# 第一次安装
pnpm install # 下载到全局 store
# 其他项目安装相同依赖
pnpm install # 直接硬链接,无需下载
Q4: 如何解决依赖冲突?
答案:
# 查看依赖树
npm ls package-name
pnpm why package-name
# 强制指定版本(npm)
# package.json
{
"overrides": {
"lodash": "4.17.21"
}
}
# yarn
{
"resolutions": {
"lodash": "4.17.21"
}
}
# pnpm
{
"pnpm": {
"overrides": {
"lodash": "4.17.21"
}
}
}
Q5: 什么是 Monorepo?如何管理?
答案:
Monorepo 是将多个项目放在同一个代码仓库中管理。
工具选择:
- pnpm workspaces:轻量级,内置支持
- yarn workspaces:yarn 内置
- Turborepo:增量构建,缓存
- Nx:功能丰富,企业级
- Lerna:经典方案
# pnpm-workspace.yaml
packages:
- 'packages/*'
- 'apps/*'
# pnpm 工作区命令
pnpm -F package-name add lodash # 为特定包安装依赖
pnpm -r run build # 所有包执行 build
pnpm -F package-name run dev # 特定包执行命令