包管理与构建指南:npm / pnpm / npx / scripts
适用场景:依赖安装与管理、npm scripts 编排、npx 执行、node_modules 排查、lock 文件冲突。
适用版本:Node.js 24 LTS、npm 11、pnpm 11;旧版本可能没有安装脚本审批等能力。
最后核对:2026-07-30
30 秒快速路径
npm ci
npm run build
npm explain package-name
目录
- 核心概念
- npm 基础操作
- pnpm — 更快更省空间
- npm exec / npx — 临时执行
- package.json scripts
- 依赖管理进阶
- 安装脚本与供应链安全
- node_modules 排查
- lock 文件
- 发布与版本
- 常见问题
核心概念
依赖类型
| 字段 | 含义 | 安装命令 |
|---|---|---|
dependencies |
运行时需要 | npm i package |
devDependencies |
仅开发/构建时需要 | npm i -D package |
peerDependencies |
声明与宿主包的兼容范围;npm 7+ 默认会尝试自动安装 | 由包管理器和宿主项目共同解析 |
optionalDependencies |
可选,安装失败不报错 | npm i -O package |
版本号语义(SemVer)
major.minor.patch
2 . 1 . 3
^ 兼容:^2.1.3 → >=2.1.3, <3.0.0(允许 minor + patch 升级)
~ 近似:~2.1.3 → >=2.1.3, <2.2.0(只允许 patch 升级)
固定: 2.1.3 → 精确这个版本
npm 基础操作
# ═══ 初始化 ═══
npm init -y # 快速创建 package.json
# ═══ 安装 ═══
npm install # 安装 package.json 中所有依赖
npm i # 简写
npm i react # 安装到 dependencies
npm i -D typescript # 安装到 devDependencies
npm i -g serve # 全局安装
npm i some-package@1.2.3 # 安装指定版本
npm i react@latest # 安装最新版
# ═══ 卸载 ═══
npm uninstall react # 移除并从 package.json 删除
npm un react # 简写
# ═══ 更新 ═══
npm outdated # 查看可更新的包
npm update # 更新所有(遵守 ^~ 范围)
npm update react # 更新单个包
# ═══ 查看 ═══
npm list # 查看已安装的依赖树
npm list --depth=0 # 只看顶层
npm info react # 查看包信息
npm info react versions # 查看所有可用版本
# ═══ 运行脚本 ═══
npm run dev # 运行 scripts.dev
npm run build # 运行 scripts.build
npm test # 特殊:可以省略 run
npm start # 特殊:可以省略 run
# ═══ 可复现安装与缓存 ═══
npm ci # 按 package-lock.json 全量安装;不改 lock
npm cache verify # 优先验证缓存完整性
# npm cache clean --force # 极少需要;确认缓存确实损坏后才清理
pnpm — 更快更省空间
pnpm 使用内容寻址存储,并通过硬链接或复制优化在同一 store/磁盘上复用包内容;具体占用取决于文件系统、store 位置与导入方式。
# 安装 pnpm
npm i -g pnpm
# ═══ 基本命令(和 npm 几乎一样)═══
pnpm install # 安装所有依赖
pnpm add react # 安装到 dependencies
pnpm add -D typescript # devDependencies
pnpm remove react # 卸载
pnpm update # 更新
pnpm run dev # 运行脚本
pnpm dev # 可以省略 run(pnpm 特性)
# ═══ pnpm 独有功能 ═══
pnpm store status # 查看全局存储状态
pnpm store prune # 清理未引用的包
pnpm why react # 查看谁依赖了 react
pnpm list --depth=0 # 顶层依赖
# ═══ monorepo(workspace)═══
# pnpm-workspace.yaml:
# packages:
# - 'packages/*'
pnpm -r run build # 所有子包执行 build
pnpm --filter @app/web dev # 指定子包运行
pnpm add lodash --filter @app/web # 给指定子包装依赖
npm vs pnpm 对比
| 方面 | npm | pnpm |
|---|---|---|
| 安装速度 | 取决于缓存、网络和项目 | 通常有较好的缓存与并发表现 |
| 磁盘占用 | 每项目独立副本 | 硬链接共享 |
| 未声明依赖访问 | 扁平布局下更容易意外访问 | 默认布局更严格,但可受 hoist 配置影响 |
| monorepo | 原生支持 npm workspaces | 原生支持 pnpm workspaces 和过滤器 |
| lock 文件 | package-lock.json | pnpm-lock.yaml |
npm exec / npx — 临时执行
npx 是 npm exec 的便捷接口,两者都能运行项目本地或从 registry 获取的包命令。远程包会进入 npm cache,并非“用完不留”;执行陌生包等同于运行第三方代码,应固定版本并确认包名和来源。
# 执行本地 node_modules/.bin 中的命令
npx tsc --init # 运行本地安装的 TypeScript
npx eslint . # 运行本地 ESLint
npm exec -- eslint . # 等价的 npm exec 写法;-- 后面是命令参数
# 交互式创建新项目时,明确选择 latest
npm create vite@latest my-app -- --template react
npm create astro@latest
# 自动化或临时工具固定已审查版本,减少意外变化
npx serve@14 dist/ # 临时起一个静态文件服务器
npx json-server@1 db.json # 临时起一个 REST API
npm exec --package=serve@14 -- serve dist/
# 结束端口监听者前先用系统命令确认 PID,不要直接运行陌生的 kill-port 包
# 指定主版本
npx create-next-app@16 # 使用已审查的 Next.js 16 脚手架
# 本地 package.json scripts 中 npx 可省略
# 因为 npm run 会自动把 node_modules/.bin 加到 PATH
package.json scripts
基础
{
"scripts": {
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"lint": "eslint src/",
"format": "prettier --write src/",
"typecheck": "tsc --noEmit"
}
}
高级模式
{
"scripts": {
"clean": "rimraf dist",
"prebuild": "npm run clean",
"build": "cross-env NODE_ENV=production astro build",
"postbuild": "node -e \"console.log('Done!')\"",
"check": "npm run lint && npm run typecheck",
"dev": "concurrently \"npm:dev:*\"",
"dev:app": "astro dev",
"dev:css": "tailwindcss -w",
"lint": "eslint",
"start": "node server.js"
}
}
prebuild/postbuild会在npm run build前后自动执行。&&串行执行,前一条失败后停止。concurrently用于并行任务。- 调用
npm run lint -- --fix时,--后的参数传给底层命令。 - 使用
rimraf、cross-env和 Node 命令,避免把 Unix 专用语法写进跨平台 scripts。
常用脚本工具
| 包 | 用途 |
|---|---|
concurrently |
并行运行多个命令 |
cross-env |
跨平台设置环境变量 |
npm-run-all |
串行/并行运行多个 scripts |
rimraf |
跨平台的 rm -rf |
wait-on |
等待端口/文件就绪再执行 |
依赖管理进阶
# ═══ 查看依赖关系 ═══
npm ls react # react 被谁引入的
npm ls --all # 完整依赖树
pnpm why react # pnpm 的 why 更清晰
# ═══ 查看包体积 ═══
# 线上工具:bundlephobia.com
npx package-size react react-dom # 查看打包大小
# ═══ 安全审计 ═══
npm audit # 查看安全漏洞
npm audit fix # 自动修复
# npm audit fix --force # 可能跨 major 升级;评估变更并测试后才考虑
# ═══ 查看全局安装了什么 ═══
npm list -g --depth=0
pnpm list -g
# ═══ 按现有 lock 干净重装 ═══
npm ci # 会自动移除现有 node_modules
# pnpm 版:保留 pnpm-lock.yaml
pnpm install --frozen-lockfile
删除 lock 文件会重新解析整棵依赖树,不应作为第一步排障。只有在明确调整依赖约束、确认 package.json 正确并准备评审完整 lock diff 时,才重新生成 lock。
安装脚本与供应链安全
依赖的 preinstall、install、postinstall 等脚本会在本机或 CI 中执行代码。除了审计已知漏洞,还要控制“谁可以在安装时运行脚本”。
# npm 11:查看、批准或拒绝依赖安装脚本
npm install-scripts ls
npm install-scripts approve sharp
npm install-scripts deny suspicious-package
# 一次性执行远程工具时,只批准确实需要脚本的包
npm exec --allow-scripts=reviewed-package --package=reviewed-package@1.2.3 -- reviewed-command
# pnpm 10.1+:交互审批依赖构建脚本
pnpm approve-builds
pnpm ignored-builds
pnpm 还可以在 pnpm-workspace.yaml 中延迟安装刚发布的版本。pnpm 11 默认 minimumReleaseAge 为 1440 分钟;显式写入可让团队策略清晰,并可用排除项处理确实需要立即升级的包:
minimumReleaseAge: 1440
minimumReleaseAgeExclude:
- '@my-org/*'
安装脚本审批、lock 文件评审、固定运行时版本和来源校验互相补充,不能用其中一项代替全部供应链检查。
node_modules 排查
# 查看 node_modules 大小
du -sh node_modules # Linux/Git Bash
# 查看哪个包最大
du -sh node_modules/* | sort -rh | head -20
# 查看某个包为什么被安装
npm explain package-name # npm 7+
pnpm why package-name
# 查看某个包的实际安装版本
node -p "require('./node_modules/react/package.json').version"
# 查看同一个包是否安装了多个版本
npm ls react --all
# 第三方静态分析工具可能误判动态 import、插件和脚本引用,删除依赖前要复核
npx depcheck
# PowerShell 查看 node_modules 大小
(Get-ChildItem node_modules -Recurse -File |
Measure-Object Length -Sum).Sum / 1MB
lock 文件
作用
lock 文件锁定依赖的精确版本,确保团队/CI 安装完全相同的依赖树。
| 包管理器 | lock 文件 |
|---|---|
| npm | package-lock.json |
| pnpm | pnpm-lock.yaml |
| yarn | yarn.lock |
规则
- lock 文件必须提交到 Git
- 不要手动编辑 lock 文件
- 不要混用包管理器(一个项目只用一种)
lock 冲突解决
# 1. 先解决并检查 package.json 冲突
git diff -- package.json
# 2. 让当前 npm 根据已解决的 package.json 更新 lock
npm install --package-lock-only
# 3. 验证 lock 与 package.json 一致,并运行测试
npm ci
npm test # 项目定义了 test script 时
# 4. 检查 lock diff 后再继续 merge/rebase
git diff -- package-lock.json
git add package-lock.json
git rebase --continue # rebase 流程
# merge 流程则正常 git commit
在 rebase 中,ours / theirs 的含义容易与直觉相反,不要不检查就对 lock 文件执行 git checkout --theirs。团队还应统一 npm 版本,减少无意义的 lock 格式抖动。
发布与版本
# ═══ 版本号管理 ═══
npm version patch # 0.1.0 → 0.1.1(自动 commit + tag)
npm version minor # 0.1.0 → 0.2.0
npm version major # 0.1.0 → 1.0.0
# ═══ 发布到 npm registry ═══
npm login # 登录
npm publish # 发布
npm publish --dry-run # 模拟发布(不实际上传)
npm unpublish package@1.0.0 # 永久操作;是否允许取决于 registry 当前政策和依赖情况
npm deprecate package@1.0.0 "请升级到 1.0.1" # 通常优先弃用错误版本
# ═══ 作用域包 ═══
npm publish --access public # @scope/package 默认是 private,需要加 --access public
常见问题
ERESOLVE — 依赖冲突
npm ERR! ERESOLVE unable to resolve dependency tree
# 先定位冲突双方及其 peerDependencies
npm explain conflicting-package
npm view conflicting-package peerDependencies
# 优先调整直接依赖版本,使 peer 范围真正兼容
# 仅在理解后果的临时场景使用:
npm i --legacy-peer-deps # 忽略 peer 约束,运行时仍可能不兼容
# npm i --force # 最后手段;必须完整测试
EACCES — 权限问题(Linux/macOS 全局安装)
# 不要用 sudo npm install -g!改用:
mkdir ~/.npm-global
npm config set prefix "$HOME/.npm-global"
export PATH=~/.npm-global/bin:$PATH # 加到 .bashrc/.zshrc
安装卡住或超时
# 切换镜像源
npm config set registry https://registry.npmmirror.com # 国内镜像
npm config get registry # 查看当前源
npm config delete registry # 恢复官方源
node 版本不兼容
# 用 nvm 管理多个 node 版本
nvm install 24
nvm use 24
nvm list # 已安装的版本
nvm alias default 24 # 设置默认版本
# Windows 用 nvm-windows 或 fnm
fnm install 24
fnm use 24