包管理器
原始素材:
2-开发环境与工具/2.07-package-managers.md
这篇解决什么问题
写代码不必从零造轮子。HTTP 请求、日期处理、表单校验、数据库连接、测试框架、构建工具,大量通用能力已经有人写好并发布到公共仓库。
包管理器要解决的是:如何找到、安装、升级、锁定和协作维护这些依赖,让项目环境可复现。
理解包管理器,可以帮你解决这些问题:
npm install到底做了什么。- 为什么同事能跑,你这里跑不起来。
package.json和 lockfile 分别负责什么。- 为什么不应该提交
node_modules。 - 全局安装和本地安装应该怎么选。
- 版本号里的
^、~、*有什么区别。 - Python 为什么需要虚拟环境。
包管理器的本质
包管理器可以理解为代码生态的「应用商店 + 安装器 + 版本记录器」。
它通常负责四件事:
- 在 Registry 中找到包。
- 下载包及其依赖。
- 安装到项目或系统环境。
- 记录精确版本,保证团队环境一致。
以 JavaScript 为例:
npm install axios
背后不是只下载一个 axios,还会解析 axios 自己依赖的包,构建完整依赖树。
各生态常见包管理器
| 生态 | 常见工具 | 配置 / 锁文件 | 说明 |
|---|---|---|---|
| JavaScript | npm、Yarn、pnpm | package.json、package-lock.json、yarn.lock、pnpm-lock.yaml |
前端和 Node.js 主生态 |
| Python | pip、conda、uv、poetry | requirements.txt、pyproject.toml、uv.lock |
注意虚拟环境隔离 |
| Rust | cargo | Cargo.toml、Cargo.lock |
构建、测试、发布一体化 |
| Go | go modules | go.mod、go.sum |
标准工具链内置 |
| macOS / Linux | Homebrew、apt、dnf / yum | 系统级包数据库 | 安装系统工具和服务 |
| Windows | winget、Chocolatey、Scoop | 各自工具配置 | 安装桌面软件和开发工具 |
不同生态命令不一样,但核心逻辑都是:声明依赖、解析版本、下载包、安装文件、记录锁定结果。
Registry:包从哪里来
Registry 是包的中央仓库。
| 生态 | Registry | 例子 |
|---|---|---|
| JavaScript | npm Registry | https://www.npmjs.com |
| Python | PyPI | https://pypi.org |
| Rust | crates.io | https://crates.io |
| Go | pkg.go.dev / module proxy | https://pkg.go.dev |
| Homebrew | formulae.brew.sh | https://formulae.brew.sh |
| Windows | winget / Chocolatey | winget.run、chocolatey.org |
企业内部也可能搭建私有 Registry,用于发布内部包、缓存公共依赖或控制安全审计。
npm、Yarn、pnpm 对比
| 工具 | 特点 | 适合场景 |
|---|---|---|
| npm | Node.js 自带,生态最通用 | 所有项目都能用,兼容性最好 |
| Yarn | 安装速度快,历史上改善 npm 体验 | 已有 Yarn 项目继续使用 |
| pnpm | 硬链接共享依赖,磁盘占用低,依赖隔离严格 | 新项目推荐,monorepo 体验好 |
简化理解:
磁盘占用:pnpm < yarn PnP < npm
安装速度:pnpm ≈ yarn > npm
通用程度:npm > pnpm > yarn
实践建议:新项目可以优先考虑 pnpm;已有项目不要轻易混用包管理器。一个项目里同时出现 package-lock.json、yarn.lock、pnpm-lock.yaml,通常意味着团队依赖管理已经开始混乱。
npm install 背后发生了什么
执行:
npm install axios
大致经过四步:
| 阶段 | 做什么 | 结果 |
|---|---|---|
| Resolve | 解析依赖树,确定要安装哪些包和版本 | 得到完整依赖图 |
| Fetch | 从 Registry 或本地缓存下载 tarball | 得到包压缩文件 |
| Link | 解压并链接到 node_modules |
项目可以引用依赖 |
| Lock | 写入 lockfile | 固定精确版本 |
这就是为什么一次安装可能会下载很多包:你安装的是直接依赖,包管理器还要处理间接依赖。
常用命令速查
JavaScript
npm install # 按 package.json 安装所有依赖
npm install axios # 安装生产依赖
npm install -D jest # 安装开发依赖
npm uninstall axios # 卸载依赖
npm update # 在版本范围内升级依赖
npm run build # 运行 scripts 中的 build
npm ci # 严格按 lockfile 安装,适合 CI
npx prettier --write src/ # 临时运行工具
pnpm 对应命令:
pnpm install
pnpm add axios
pnpm add -D vitest
pnpm remove axios
pnpm run build
pnpm dlx create-vue
Python
pip install requests
pip install requests==2.28.0
pip freeze > requirements.txt
pip install -r requirements.txt
Rust
cargo add serde
cargo build
cargo test
cargo run
Go
go get github.com/gin-gonic/gin
go mod tidy
go build ./...
系统工具
brew install git
winget install Git.Git
winget upgrade --all
package.json 和 npm scripts
package.json 是 JavaScript 项目的依赖和脚本声明文件。
{
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "vitest",
"lint": "eslint src/"
},
"dependencies": {
"axios": "^1.6.8"
},
"devDependencies": {
"typescript": "^5.4.0"
}
}
运行:
npm run dev
npm run build
npm scripts 的价值:
- 团队统一命令入口。
- 自动把
node_modules/.bin加入 PATH。 - 不需要每个人记住底层工具参数。
- CI 可以直接复用同一套命令。
生产依赖和开发依赖
| 类型 | 字段 | 例子 | 用途 |
|---|---|---|---|
| 生产依赖 | dependencies |
axios、react、express |
运行时需要 |
| 开发依赖 | devDependencies |
typescript、eslint、vitest |
只在开发、测试、构建时需要 |
判断标准:部署后的应用运行时还需要它吗?需要就放 dependencies,只用于构建和检查就放 devDependencies。
本地安装 vs 全局安装
npm install axios # 本地安装到当前项目
npm install -g typescript # 全局安装到系统环境
| 对比 | 本地安装 | 全局安装 |
|---|---|---|
| 存放位置 | 当前项目 node_modules |
系统级目录 |
| 版本隔离 | 每个项目独立 | 全机共用 |
| 团队一致性 | lockfile 保证 | 依赖每个人本机环境 |
| 适合对象 | 项目库、构建工具、测试工具 | 少数通用 CLI |
黄金法则:
库类依赖永远本地安装;命令行工具优先本地安装,用
npx、pnpm dlx或 scripts 调用。
原因很简单:项目 A 可能需要 ESLint 8,项目 B 需要 ESLint 9。工具本地化才能让项目环境稳定。
npx、pnpm dlx 和临时运行
临时运行工具,不污染全局环境:
npx create-vue my-project
npx prettier --write src/
npx typescript@5.4 tsc --version
pnpm dlx create-vue
Python 生态里 uvx 也有类似用途:
uvx ruff check .
语义化版本
语义化版本(SemVer)格式是:
MAJOR.MINOR.PATCH
例如:
2.8.3
| | |
| | └─ PATCH:修复 Bug,通常兼容
| └─── MINOR:新增功能,通常兼容
└───── MAJOR:破坏性变更,升级要谨慎
常见版本范围:
| 写法 | 含义 | 例子 |
|---|---|---|
1.6.8 |
精确版本,只接受这个版本 | 最严格 |
^1.6.8 |
允许 minor 和 patch 升级,不升级 major | 推荐常见写法 |
~1.6.8 |
只允许 patch 升级 | 更保守 |
* |
任意版本 | 生产环境避免 |
最佳实践:package.json 用 ^ 声明兼容范围,lockfile 固定实际安装版本。
lockfile:团队协作的基石
如果 package.json 里写:
{
"dependencies": {
"axios": "^1.6.0"
}
}
不同时间安装可能得到不同版本。lockfile 会记录实际安装的精确版本和依赖树,保证团队、CI、生产环境一致。
| 文件 | 生态 |
|---|---|
package-lock.json |
npm |
yarn.lock |
Yarn |
pnpm-lock.yaml |
pnpm |
requirements.txt / lock 文件 |
Python |
Cargo.lock |
Rust |
go.sum |
Go |
常见命令:
| 场景 | 命令 | 行为 |
|---|---|---|
| 本地开发 | npm install |
参考 lockfile 安装,必要时更新 |
| CI / 生产构建 | npm ci |
严格按 lockfile 安装,不一致就失败 |
| 主动升级 | npm update |
在允许范围内升级并更新 lockfile |
lockfile 要不要提交?
- Web 应用、后端服务:必须提交。
- npm 发布的库:视团队策略,库消费者会有自己的 lockfile。
- Go 项目:
go.sum必须提交。 - Python 项目:依赖声明和锁定文件应提交。
依赖地狱和幽灵依赖
依赖树可能非常大。你的项目依赖 50 个包,这 50 个包又依赖几百个包。
常见问题:
- 版本冲突:两个包需要同一个依赖的不同主版本。
- 重复安装:同一依赖出现多个版本。
- 幽灵依赖:你没有在
package.json声明,却因为被提升到顶层而能 import。 - 安全漏洞:间接依赖里有已知漏洞。
不同生态的处理方式:
| 工具 | 处理思路 |
|---|---|
| npm | 依赖提升,尽量共享 |
| pnpm | 硬链接共享 + 严格隔离,减少幽灵依赖 |
| Cargo | 通过版本规则和锁文件管理冲突 |
| Go modules | 最小版本选择 MVS |
Python 虚拟环境
Python 默认容易把包装到全局环境。不同项目需要不同版本时,就会互相污染。
创建虚拟环境:
python -m venv .venv
激活:
# macOS / Linux
source .venv/bin/activate
# Windows CMD
.venv\Scripts\activate
# Windows PowerShell
.venv\Scripts\Activate.ps1
安装依赖:
pip install requests
退出:
deactivate
PowerShell 如果禁止脚本执行:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
.venv 不要提交到 Git。应提交 requirements.txt、pyproject.toml 或对应 lock 文件。
常见问题
node_modules 要提交到 Git 吗
不要。它体积大、平台相关、可通过 lockfile 重新安装。应写入 .gitignore:
node_modules/
安装失败怎么办
先确认 Node / npm / pnpm 版本,再清理重装:
npm cache clean --force
rm -rf node_modules package-lock.json
npm install
Windows CMD:
rmdir /s /q node_modules
del package-lock.json
npm install
安装速度慢怎么办
推荐项目级配置,不污染全局:
echo "registry=https://registry.npmmirror.com" > .npmrc
Python 临时指定镜像:
pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple
安全漏洞怎么办
npm audit
npm audit fix
npm audit fix --force
--force 可能引入破坏性升级,使用前要看变更和跑测试。
如何判断一个包是否值得信赖
检查这些信号:
- 周下载量。
- 最近更新时间。
- GitHub Stars 和 Issues 活跃度。
- 维护者是否可信。
- 依赖数量是否过多。
- 是否有安全漏洞记录。
- 包体积是否合理。
名词对照表
| 英文术语 | 中文对照 | 解释 |
|---|---|---|
| Package | 包 / 库 | 别人写好并发布的代码模块。 |
| Registry | 注册表 / 仓库 | 所有包的中央存储服务。 |
| Dependency | 依赖 | 项目运行所需的其他包。 |
| devDependency | 开发依赖 | 只在开发、测试、构建阶段需要的包。 |
| Lockfile | 锁文件 | 记录精确版本和依赖树,保证环境一致。 |
| SemVer | 语义化版本 | MAJOR.MINOR.PATCH 版本规范。 |
| node_modules | 模块目录 | npm 安装依赖的实际目录。 |
| venv | 虚拟环境 | Python 项目的独立依赖环境。 |
| tarball | 压缩包 | 包发布和下载时常见的 .tgz 格式。 |
| Hoisting | 依赖提升 | npm 将部分子依赖提升到顶层目录。 |
| Phantom Dependency | 幽灵依赖 | 未声明却因为依赖提升而能使用的包。 |
| npx | 包运行器 | 临时运行 npm 包,无需全局安装。 |
| Crate | Rust 包 | Rust 生态中包的单位。 |
| winget | Windows 包管理器 | Windows 官方软件安装工具。 |
小结
- 包管理器帮你下载、安装、管理和锁定依赖。
- Registry 是包的来源,lockfile 是团队环境一致性的关键。
- 项目依赖优先本地安装,少用全局安装。
- 语义化版本要和 lockfile 配合使用。
- Python 项目要用虚拟环境隔离依赖。
- 依赖安全和依赖体积也是工程质量的一部分。