跳转到内容

第 14 章 · 包管理器对照

一句话:npm、pnpm、pip、uv、dotnet、go 这几个包管理器把清单文件里写的依赖解析成确切版本、下载到本地,同一件事在四种生态里的命令对照着查。 用它还是用别的:Node 项目看仓库里有哪个锁文件,package-lock.json 用 npm,pnpm-lock.yaml 用 pnpm;Python 新项目用 uv,只有 requirements.txt 的老项目在虚拟环境里用 pip;.NET 用 dotnet 自带的 NuGet 命令,Go 用 go 自带的模块命令,不另装工具。 核对版本:npm 10.9(Node 22)、pnpm 12、pip 26.1、uv 0.12、.NET SDK 10.0、Go 1.27,2026-10

多数包管理器跟着语言运行时一起装好,语言本身的安装命令见第 3 章 3.1。

工具 跟着谁来 查版本
npm 随 Node.js npm --version
pnpm 单独装,命令见下 pnpm --version
pip 随 Python python -m pip --version
uv 单独装,命令见下 uv --version
dotnet 随 .NET SDK dotnet --version
go 随 Go go version

uv 是一个独立的可执行文件,不依赖已装的 Python:

终端窗口
winget install --id astral-sh.uv -e
终端窗口
brew install uv # macOS
curl -LsSf https://astral.sh/uv/install.sh | sh # Linux

pnpm 按官方文档用 npm 安装为独立程序,需 Node 22.13 以上,三个平台相同;以后升级用 pnpm self-update:

终端窗口
npx get-pnpm
  • 清单文件(Manifest):手写的依赖列表,写的是版本范围,如 package.json、pyproject.toml、*.csproj、go.mod。要提交进仓库。
  • 锁文件(Lockfile):工具解析版本范围后,把每个包(包括依赖的依赖)的确切版本和校验和记下来,如 package-lock.json、uv.lock。要提交,否则两台机器装出来的版本可能不同。NuGet 默认不生成,要在项目里开启(见 14.5)。Go 的 go.mod 本身就写最低确切版本,go.sum 只存校验和。
  • 版本范围:npm 的 ^1.2.3 允许升到 2.0.0 以下,~1.2.3 只允许 1.2.x;Python 的 ==2.31.0 锁死,>=2.31 不封顶,uv add 默认写 >=;NuGet 的 Version="13.0.1" 表示最低 13.0.1;Go 取所有依赖要求里最低能满足的版本。
  • 开发依赖:只在开发和测试时用的包,如 TypeScript、pytest。npm 记在 devDependencies,生产安装 npm ci --omit=dev 跳过它们;uv 记在 [dependency-groups] 的 dev 组。.NET 没有单独的开发依赖命令,只在构建时用的包(如分析器)加 PrivateAssets="all",测试包装在测试项目里;Go 的工具依赖用 tool 指令记录。
  • 全局与项目本地:依赖装在项目里(node_modules/、.venv/),互不干扰。全局装的是命令行工具,命令放在一个单独的目录里,这个目录要在 PATH 上,见 14.6。
  • 镜像源(Registry):存放包的服务器,默认是 npmjs.org、PyPI、nuget.org、proxy.golang.org。国内访问慢时换成国内镜像源,包内容相同。

下图上半是四种生态共同的流程,下面四行是各自的文件名:

清单文件经解析生成锁文件,再按锁文件从镜像源下载装成本地依赖;npm、uv、dotnet、go 各自的三个文件或目录

表中包名都是示例。uv 的命令要在有 pyproject.toml 的目录里运行,dotnet 的在 *.csproj 所在目录。

任务 npm uv dotnet go
初始化项目 npm init -y uv init dotnet new console go mod init example.com/app
加依赖 npm install lodash uv add requests dotnet add package Newtonsoft.Json go get github.com/google/uuid
加指定版本 npm install lodash@4.17.21 uv add "requests==2.31.0" dotnet add package Newtonsoft.Json --version 13.0.1 go get github.com/google/uuid@v1.5.0
加开发依赖 npm install -D typescript uv add --dev pytest 无,测试包装在测试项目里 go get -tool golang.org/x/tools/cmd/stringer
删依赖 npm uninstall lodash uv remove requests dotnet remove package Newtonsoft.Json go get github.com/google/uuid@none
按锁文件装,不改锁文件 npm ci uv sync --locked dotnet restore --locked-mode go mod download
改了清单后更新锁文件 npm install uv lock dotnet restore;--force-evaluate 全部重新解析 go mod tidy
升级到范围内最新 npm update uv lock --upgrade 再 uv sync dotnet package update go get -u ./...
列出可升级的包 npm outdated uv tree --outdated --depth 1 dotnet package list --outdated go list -m -u all
列出已装的包 npm ls --depth=0 uv tree --depth 1 dotnet package list go list -m all
运行 npm run <脚本名> uv run pytest dotnet run go run .
清缓存 npm cache clean --force uv cache clean dotnet nuget locals all --clear go clean -modcache
换镜像源 npm install --registry=<url> uv sync --default-index <url> dotnet restore --source <url> go env -w GOPROXY=<url>,direct

最后一行前三格只对这一条命令生效,Go 那一格写进用户配置、长期生效;长期配置见 14.4。

pnpm 的写法和 npm 基本一样,不同的是:加依赖 pnpm add(开发依赖 pnpm add -D),删依赖 pnpm remove,按锁文件装 pnpm install --frozen-lockfile(CI 环境里默认就是),清缓存 pnpm store prune。pip 的锁文件(pip lock,pylock.toml)还是实验功能,常用 pip freeze > requirements.txt 记下确切版本,pip install -r requirements.txt 照着装;其余在虚拟环境里用 pip install requests、pip uninstall requests、pip install -U requests、pip list --outdated、pip cache purge,换源加 -i <url>。

拿到别人的项目,先按锁文件装

Section titled “拿到别人的项目,先按锁文件装”

只装锁文件里的版本,锁文件和清单对不上就报错,不会悄悄改锁文件。CI 里用 npm ci,见第 10 章。

  1. 克隆并进入项目:git clone <url>。
  2. Node 项目,按 package-lock.json 装,已有的 node_modules/ 会先被整个删掉:npm ci。
  3. Python 项目,自动建 .venv 并按 uv.lock 装:uv sync --locked。
  4. 只有 requirements.txt 的 Python 项目,先建并激活虚拟环境(第 3 章 3.4),再装:python -m pip install -r requirements.txt。
  5. .NET 项目,有 packages.lock.json 时:dotnet restore --locked-mode。
  6. Go 项目,下载 go.mod 列的模块并核对 go.sum:go mod download。
  7. 跑一下测试确认环境齐了:npm test、uv run pytest、dotnet test 或 go test ./...。

下面的地址是常用的例子,除第 3 步外都写进用户级配置,对所有项目生效;只想对一个项目生效,写进项目里的配置文件(见 14.5)。

  1. npm(pnpm 也读这个设置):npm config set registry https://registry.npmmirror.com/。
  2. pip:python -m pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple。
  3. uv 不读 pip 的配置,把 14.5 的最小 uv.toml 放到用户级位置;只对这一次生效时加参数:uv sync --default-index https://mirrors.aliyun.com/pypi/simple/。
  4. NuGet 加华为云镜像源:dotnet nuget add source https://mirrors.huaweicloud.com/repository/nuget/v3/index.json -n huawei。
  5. 停用 nuget.org,否则它连不上时还原照样报错:dotnet nuget disable source nuget.org。
  6. Go:go env -w GOPROXY=https://goproxy.cn,direct。
  7. 核对生效:npm config get registry、python -m pip config list、dotnet nuget list source、go env GOPROXY。

同一台机器上用多个运行时版本

Section titled “同一台机器上用多个运行时版本”
  1. Node 用 fnm(Windows 也可用 nvm-windows),先装 fnm:winget install Schniz.fnm。
  2. 按 fnm 文档把 fnm env --use-on-cd 加进 shell 配置文件后,装并切换 Node 版本:fnm install 22 再 fnm use 22。
  3. Python 不用另装管理器,uv 自己下载解释器:uv python install 3.14。
  4. 让本项目固定用这个版本,写进 .python-version:uv python pin 3.14。
  5. .NET 多个 SDK 可以并存,在项目根目录写 global.json 指定用哪个:dotnet new globaljson --sdk-version 10.0.401 --roll-forward latestFeature。
  6. 核对当前目录用的 SDK:dotnet --version。
  7. Go 在 go.mod 写了更高的 go 版本时,go 命令会自动下载对应工具链;要并存一个旧版本:go install golang.org/dl/go1.26.0@latest,再运行 go1.26.0 download。

项目级配置放在项目根目录,随仓库提交,优先于用户级。

配置项 项目级 用户级 Windows 用户级 macOS / Linux
npm、pnpm .npmrc %USERPROFILE%\.npmrc ~/.npmrc
pip 虚拟环境目录里的 pip.ini(Linux/macOS 为 pip.conf) %APPDATA%\pip\pip.ini ~/.config/pip/pip.conf
uv uv.toml 或 pyproject.toml 的 [tool.uv] %APPDATA%\uv\uv.toml ~/.config/uv/uv.toml
NuGet nuget.config %APPDATA%\NuGet\NuGet.Config ~/.nuget/NuGet/NuGet.Config
Go 无,用环境变量 go env -w 写入 %APPDATA%\go\env ~/.config/go/env(macOS 为 ~/Library/Application Support/go/env)
NuGet 锁文件开关 *.csproj 里的 RestorePackagesWithLockFile — —

pnpm 只从 .npmrc 读镜像源和认证,下面片段的后两项只对 npm 生效。NuGet 要生成 packages.lock.json,在 *.csproj 的 <PropertyGroup> 里加一行 <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>,再 dotnet restore。

最小 .npmrc,放在项目根目录:

# 从国内镜像源下载;锁文件里会记下这个地址
registry=https://registry.npmmirror.com/
# npm install 写入确切版本,不加 ^
save-exact=true
# 当前 Node 版本不符合 package.json 的 engines 时直接报错
engine-strict=true

最小 uv.toml,放在 pyproject.toml 旁边:

# 默认索引换成国内镜像源,uv add、uv lock、uv sync 都从这里下载
[[index]]
url = "https://pypi.tuna.tsinghua.edu.cn/simple"
default = true # 取代 PyPI,而不是在 PyPI 之外再加一个
  • npm ci 报 EUSAGE,说 package.json and package-lock.json ... are in sync。 原因:改了 package.json,锁文件没跟着更新。处理:本机 npm install 后提交锁文件;uv 报 but --locked was provided 时运行 uv lock。
  • 全局装的命令报 The term 'tsc' is not recognized 或 command not found。 原因:全局工具目录不在 PATH 上。处理:用 npm prefix -g、uv tool dir --bin、go env GOPATH(其下 bin)查目录,dotnet 是 ~/.dotnet/tools,加进 PATH 后重开终端。
  • Linux 上 pip install 报 error: externally-managed-environment;Windows 上包装进了系统 Python,别的项目跟着变。 原因:没进虚拟环境,pip 装到了系统 Python。处理:先 python -m venv .venv 并激活再装,或改用 uv add,uv 自动用项目的 .venv。
  • 装包卡住或超时:ETIMEDOUT、ReadTimeoutError、NU1301、i/o timeout。 原因:默认镜像源在国外,访问慢或连不上。处理:按 14.4 配国内镜像源,npm config get registry 或 go env GOPROXY 核对确实生效。
  • 合并分支后锁文件里出现 <<<<<<< 冲突标记。 原因:两边都改了依赖,锁文件是生成的,手改容易漏掉依赖的依赖。处理:先解决清单文件的冲突,再取一边的锁文件重新生成:git checkout --theirs package-lock.json 后 npm install(uv 用 uv lock,Go 用 go mod tidy)。
  • npm 命令参考:npm install、ci、update、config 等每个命令的参数,.npmrc 的全部配置项也在这里。
  • uv 文档:项目、依赖、锁文件、Python 版本管理和 uv.toml 的写法,先看 Projects 一节。
  • pip 用户指南:requirements 文件、配置文件位置、换索引和缓存。
  • NuGet 文档:dotnet 的包引用、nuget.config、锁文件和包源的配置。
  • Go Modules 参考:go.mod、go.sum、最小版本选择、GOPROXY 和 go get 的完整说明。