npm 依赖类型全解析:dependencies、devDependencies 和 peerDependencies 怎么选
package.json 里有 dependencies、devDependencies、peerDependencies、optionalDependencies——都叫依赖,到底什么区别?该往哪个里装?装错了会怎样?这篇一次讲清楚。
dependencies vs devDependencies:唯一的本质区别
生产环境装不装——就这么简单。
| dependencies | devDependencies | |
|---|---|---|
npm install | 安装 | 安装 |
npm install --production | 安装 | 不安装 |
npm ci --production | 安装 | 不安装 |
NODE_ENV=production npm install | 安装 | 不安装 |
dependencies:应用运行时必需的包(express、axios、lodash)devDependencies:只在开发和构建时需要的包(jest、eslint、typescript、webpack)
怎么判断放哪里
问自己一个问题:这个包如果不在,应用还能跑吗?
- 能跑 →
devDependencies(测试框架、代码检查、构建工具) - 不能跑 →
dependencies(Web 框架、数据库驱动、日期库)
一个容易搞混的例子
TypeScript 放哪?
- 应用项目:
devDependencies——运行时不需要 TypeScript,只需要编译产物 - 库项目(npm 包):
devDependencies——用户装你的包不需要 TypeScript
@types/xxx 呢?也是 devDependencies——类型声明只在编译时用。
peerDependencies:我需要你,但我不装你
peerDependencies 是给库/插件用的,告诉宿主项目"你需要安装这个依赖,我自己不装"。
json// react-component-lib 的 package.json { "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }
为什么不直接放 dependencies?因为 React 只能有一个实例。如果组件库自己装了一份 React,应用也装了一份,运行时会有两个 React 副本——hooks 会炸。
npm v7 以前 vs 现在
- npm v6:peerDependencies 不满足只会警告,照样安装
- npm v7+:peerDependencies 不满足会报错,安装失败
这导致很多老项目升级 npm 后突然装不上依赖了。解决方案:
bashnpm install --legacy-peer-deps # 回退到 v6 的行为
常见需要 peerDependencies 的场景
- UI 组件库依赖 React/Vue/Angular
- Babel 插件依赖 @babel/core
- ESLint 插件依赖 eslint
- Webpack loader 依赖 webpack
原则:你的包作为插件扩展另一个包时,被扩展的包放在 peerDependencies。
optionalDependencies:装不上也没关系
json{ "optionalDependencies": { "fsevents": "^2.3.0" } }
安装失败不会中断整个 npm install——只是这个包不可用,调用时需要自己做容错:
javascriptlet fsevents; try { fsevents = require('fsevents'); } catch { // 回退到其他方案 }
典型场景:fsevents 只在 macOS 上可用,Linux/Windows 上装不了但也不影响功能——用其他文件监听方案兜底。
注意:不要滥用。大部分依赖是必须的,装不上就应该报错而不是静默跳过。
bundledDependencies:打包进你的发布包
json{ "bundledDependencies": ["my-helper-lib"] }
正常情况下 npm install 你的包时,依赖会从 registry 下载。但 bundledDependencies 里的包会被直接打包到你的发布文件中,安装时不需要从 registry 下载。
用途很少——主要是某些包不在公共 registry 上,又不想让用户单独配置私有源。
版本号规则:^ vs ~ vs 精确版本
json{ "dependencies": { "express": "^4.18.0", "lodash": "~4.17.0", "react": "18.2.0" } }
| 写法 | 允许的版本范围 | 例子 |
|---|---|---|
^4.18.0 | 兼容的次版本更新 | 4.18.0 ~ 4.x.x(不会升到 5.0) |
~4.17.0 | 兼容的修订版本更新 | 4.17.0 ~ 4.17.x(不会升到 4.18) |
4.18.0 | 精确版本 | 只能用 4.18.0 |
^ 是默认行为(npm install 自动加),意味着次版本和修订版本的更新都会被接受。这通常没问题,但如果某个次版本更新引入了 bug,你的项目可能在别人那能跑在你这跑不了——这就是为什么需要 package-lock.json 锁定精确版本。
实际项目中的依赖配置建议
应用项目(Web 应用、后端服务)
dependencies:运行时必需的包devDependencies:构建工具、测试、lint- 不需要
peerDependencies和optionalDependencies
库项目(npm 包、组件库)
dependencies:库运行时必需且不会被宿主重复安装的包devDependencies:构建工具、测试、文档peerDependencies:宿主项目应该提供的包(React、Webpack 等)optionalDependencies:平台特定的可选增强
依赖类型选择流程
shell这个包运行时需要吗? ├── 不需要 → devDependencies ├── 需要 → 宿主项目可能已经安装了吗? │ ├── 是 → peerDependencies │ └── 否 → 装不上也行吗? │ ├── 是 → optionalDependencies │ └── 否 → dependencies