Node.js 包版本约束与依赖类型
package.json 里的版本号不是装饰——它决定下游装你的包时会拿到什么。
- SemVer 三段式:major.minor.patch,递增规则:破坏性变更 / 新功能 / bugfix
- 范围符号核心:4 个最常用 ——
^~*>= - 0.x.x 陷阱:
^0.6.6实际只升 patch,不升 minor - 5 种依赖类型:dependencies / devDeps / peerDeps / optionalDeps / bundledDeps
- peerDeps 行为变化:npm 7+ 默认自动安装(npm 3-6 还会警告)
- 现代替代:
overrides(npm)/resolutions(Yarn)取代 bundledDeps
SemVer 不是"三个数字"
Node.js 全套包管理工具都遵循 SemVer 2.0.0 三段式:major.minor.patch。
递增规则(递增时把后面段归零):
- major:破坏性变更,不兼容老版本
- minor:新功能,向后兼容
- patch:bugfix,向后兼容
例:2.6.6 的下一个 minor 是 2.7.0(minor +1,patch 归零)。规范本身不难,下面这些 范围符号 才是真正用错的坑。
范围符号:^ ~ * >=
| 符号 | 语义 | 例子 | 实际匹配范围 |
|---|---|---|---|
^ | 升 minor + patch(npm/yarn 默认) | ^2.6.6 | >=2.6.6 <3.0.0 |
~ | 升 patch | ~2.6.6 | >=2.6.6 <2.7.0 |
* | 任意版本 | * | >=0.0.0 |
>= | 大于等于 | >=2.6.6 | >=2.6.6 |
< > <= 也支持,但粗粒度控制用得不多,所有版本约束都能用裸数字(如 "lodash": "2.6.6" 锁死单个版本)。
0.x.x 的特殊约定
0.y.z 阶段被认为不稳定,所以 ^ 在这里不升 minor。
^0.6.6实际范围:>=0.6.6 <0.7.0(只升 patch)^0.6实际范围:>=0.6.0 <0.7.0
原因是 0.x 的 minor 通常会破 API,跟成熟版本里 minor 等于"向后兼容新功能"的语义不一样。维护 0.x 包要时刻记着这条,否则用户装了不会拿到你期望的更新范围。
5 种依赖类型
dependencies — 运行时必需。发库时下游 npm install 必装。
devDependencies — 开发期专用(构建、测试、lint)。如果是 应用 而非库,本地 npm install 全装;如果发库,下游只装 dependencies,dev 留给 fork 的人自己装。
peerDependencies — 声明 "我 不自装,指望宿主环境提供"。典型场景:React 插件声明对宿主 React 版本范围的最低要求。
- npm 3-6:自动装到
node_modules但会强烈警告(除非装 7+ 时代的peerDependenciesMeta) - npm 7+:默认自动安装,不再警告,写法只需声明最低版本
optionalDependencies — 装失败也不报错。适合"装不上有 fallback"的场景,比如原生编译失败时 JS 实现顶替。
bundledDependencies — 发布时把指定包一起打进 tarball。2026 现状:基本被废弃,由 exports field 和下面说的 overrides 接管,新项目不要用。
现代替代:overrides(npm)/ resolutions(Yarn)
需要锁定某个传递依赖的版本?2026 年的标准做法是 npm 的 overrides:
{
"overrides": {
"lodash": "4.17.21"
}
}
这 把整棵依赖树里的 lodash 强制锁到 4.17.21,不管哪个上游包用到它。比 bundledDependencies 灵活(不限于直接依赖),比 fork 整个依赖树省事 100 倍。
Yarn 的等价字段是 resolutions(yarn classic);pnpm 用同名 overrides 但语义更严格(pnpm overrides)。
反例:peer 范围写宽的下场
一个常见踩坑:
同事写了一个 React 组件库,在
peerDependencies写了^1.2.0,意思是">=1.2.0任意最新"。下游用户的 React 是1.5.x,某天他 bump 到2.0,结果你代码里用到1.4.0才有的 API 全部炸掉。
问题出在 误解 ^1.2.0 的范围。实际上 ^1.2.0 = >=1.2.0 <2.0.0,1.x 内部的所有 minor 升级都在范围内。你不能假设 ^1.x.x 跟 ~1.x.x 一样只升 patch。
peer 范围宁严勿松,只写实际测过的最低 minor 上限,比如 >=1.4.0 <1.5.0。写完 实际跑一次下游安装,确认范围里每个 minor 都能编译过。
References
- Semantic Versioning 2.0.0 —— 三段式规范本身
- npm overrides 字段文档 —— 锁传递依赖的现代方式
- pnpm overrides 字段文档 —— pnpm 等价字段,语义更严格
- Yarn resolutions 字段文档 —— Yarn classic 等价字段