Skip to main content

Node.js 包版本约束与依赖类型

· 4 min read

package.json 里的版本号不是装饰——它决定下游装你的包时会拿到什么。

  1. SemVer 三段式:major.minor.patch,递增规则:破坏性变更 / 新功能 / bugfix
  2. 范围符号核心:4 个最常用 —— ^ ~ * >=
  3. 0.x.x 陷阱:^0.6.6 实际只升 patch,不升 minor
  4. 5 种依赖类型:dependencies / devDeps / peerDeps / optionalDeps / bundledDeps
  5. peerDeps 行为变化:npm 7+ 默认自动安装(npm 3-6 还会警告)
  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 vs 7+ 行为分水岭
  • 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​

  1. Semantic Versioning 2.0.0 —— 三段式规范本身
  2. npm overrides 字段文档 —— 锁传递依赖的现代方式
  3. pnpm overrides 字段文档 —— pnpm 等价字段,语义更严格
  4. Yarn resolutions 字段文档 —— Yarn classic 等价字段