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 的等价字段是 resolutionsyarn 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.01.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 等价字段