跳到主要内容

包管理器

问题

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大于等于
x4.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 使用硬链接和符号链接,节省磁盘空间,安装速度更快。

优势

特性npmyarnpnpm
磁盘空间每项目独立每项目独立共享存储
安装速度最快
幽灵依赖存在存在不存在
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 文件
npmpackage-lock.json
yarnyarn.lock
pnpmpnpm-lock.yaml
最佳实践
  • Lock 文件应该提交到版本控制
  • 团队使用相同的包管理器
  • CI/CD 使用 npm ci / yarn install --frozen-lockfile / pnpm install --frozen-lockfile

常见面试问题

Q1: npm install 和 npm ci 有什么区别?

答案

特性npm installnpm 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 快?

答案

  1. 硬链接:所有包存储在全局 store,项目通过硬链接引用
  2. 符号链接:依赖关系通过符号链接组织
  3. 并行下载:更高效的并行下载策略
  4. 避免重复:相同版本只下载一次
# 第一次安装
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 # 特定包执行命令

相关链接