刚接触 npm 包管理时,很多人都会被两个字段绕住:
dependenciesdevDependencies
表面看只差了一个 dev,但实际背后是两种完全不同的角色。
很多人会先形成一个直觉:
devDependencies是开发时依赖,dependencies是运行时依赖。
这个理解方向没错,但如果只停在这里,实际写项目时还是容易分错。
这篇文章就把这个问题彻底掰开讲明白。
一、先看结论
最简单的判断标准是这一句:
包在真正运行时还需要它,就放
dependencies;
只在开发、测试、构建、打包阶段需要它,就放devDependencies。
换句话说:
dependencies
表示运行时依赖
也就是:
- 项目启动时需要
- 代码执行时需要
- 用户真正使用功能时需要
devDependencies
表示开发时依赖
也就是:
- 本地开发时需要
- 测试时需要
- 构建时需要
- 打包发布时需要
- 代码检查时需要
二、为什么这个问题总让人混淆
因为“开发时会用到”这句话,范围太大了。
你写代码的时候,当然什么都在开发时用到了:
- 你会用
axios - 你会用
lodash - 你会用
typescript - 你会用
eslint
但它们的性质并不一样。
这里真正该问的,不是:
我开发时有没有用到它?
而是:
我把包发布出去后,别人安装并运行这个包时,还需不需要它?
这才是判断关键。
三、先用一个生活化的比喻理解
可以把开发 npm 包想成开一家小餐馆。
dependencies 是什么
像端上桌的食材:
- 面
- 肉
- 菜
- 调料
没有这些,顾客吃不到东西。
devDependencies 是什么
像后厨工具:
- 菜刀
- 烤箱
- 称
- 清洁工具
它们对做菜很重要,但顾客不需要把这些工具一起买走。
所以:
- 跟着“成品”一起发挥作用的,是
dependencies - 只在“制作过程”中发挥作用的,是
devDependencies
四、最常见的理解方式
很多文章会这样解释:
dependencies
项目运行时要用的依赖
devDependencies
开发这个项目时要用的依赖
这句话没错,但还不够完整。
更准确一点,应该改成:
dependencies
项目在实际运行时必须存在的依赖
devDependencies
项目在开发、测试、构建、打包、发布时使用的依赖
注意这里多出来几个关键词:
- 测试
- 构建
- 打包
- 发布
这几个词很重要,因为很多初学者只理解了“开发”,没理解“构建”。
五、什么叫“构建时需要”?
很多人第一次看到这句话会疑惑:
TypeScript、Babel 明明不参与业务运行,为什么它们很重要?
原因很简单:
你平时写的源码,不一定是最终发布给别人运行的代码。
比如你写的是:
const add = (a: number, b: number) => a + b
export default add
这里有 TypeScript 类型标注,运行环境并不能直接拿这些类型来执行。
所以发布前,通常会经过一轮处理,变成:
const add = (a, b) => a + b
export default add
或者再进一步转成更兼容旧环境的版本。
这个“把源码处理成可发布产物”的过程,就是构建或编译。
所以:
typescriptbabelrollupwebpackvite
这些通常属于 devDependencies。
因为它们负责的是“做菜过程”,不是“上桌后的食物”。
六、最容易分清的一种办法
每次遇到一个依赖,不妨问自己一句:
如果把项目构建完、发布出去,用户真正使用功能时,还要不要这个依赖?
如果要
放进 dependencies
如果不要,只是帮助你开发和打包
放进 devDependencies
这个方法很稳。
七、通过例子理解
例子 1:工具函数库里用了 dayjs
import dayjs from 'dayjs'
export function formatDate(date) {
return dayjs(date).format('YYYY-MM-DD')
}
这里 dayjs 应该放哪?
答案是:dependencies
因为你的函数真正运行时,需要调用 dayjs。
不是你开发时用了它一次就结束了,而是你包的使用者在调用 formatDate 时,底层还要依赖 dayjs。
例子 2:用了 eslint 做代码检查
开发时你装了:
npm install eslint -D
它是用来做什么的?
- 检查代码规范
- 提示潜在问题
- 统一团队风格
项目真正运行时,需要 eslint 吗?
不需要。
所以它应该放进 devDependencies。
例子 3:用了 jest 或 vitest 做测试
测试工具只在测试阶段执行。
用户安装你的包,并不会因为要调用某个功能而去运行 jest。
所以这类依赖一般都属于 devDependencies。
例子 4:用了 typescript 写源码
你源码可能是 .ts 文件,但最终发布的包往往是已经编译好的 .js 文件。
也就是说,用户使用你包时,用到的是产物,不是你本地的 TypeScript 编译器。
所以 typescript 一般属于 devDependencies。
例子 5:项目里使用 axios 发请求
如果你的业务代码里直接这样写:
import axios from 'axios'
export function getUser() {
return axios.get('/api/user')
}
那 axios 是实际运行逻辑的一部分。
因此它通常属于 dependencies。
八、为什么很多人会把依赖放错
常见原因主要有三个。
1)只从“我开发时有没有用过”来判断
这会导致判断范围过大。
因为你开发时什么都用到了,但不是所有东西都会进入运行阶段。
2)没有区分“源码”和“发布产物”
这是个很常见的坑。
很多工具只作用在源码阶段,比如:
- 类型检查
- 代码转换
- 打包压缩
- 生成声明文件
这些工具非常重要,但它们的重要性停留在“生产过程”,不是“运行过程”。
3)把“业务依赖”误认为“开发依赖”
比如平时有的产品业务里明确用到了:
axioslodashdayjs
这些都不应该因为“开发时也在用”就被扔进 devDependencies。
它们是运行逻辑本身的一部分。
九、从包作者和包使用者两个视角看,会更清楚
这个问题最容易绕,是因为视角没切换。
站在包作者视角
你会觉得:
- 我开发时用了
typescript - 我开发时也用了
axios - 我开发时也用了
eslint
感觉它们都是“开发中会用到的东西”。
没错,但这不是 npm 关心的重点。
站在包使用者视角
npm 更关心的是:
- 用户安装你的包后,哪些依赖必须存在,代码才能跑
- 哪些依赖只是你内部研发流程要用
这样一看,边界就清楚了:
- 运行必须的 →
dependencies - 研发辅助的 →
devDependencies
十、一个最小的项目例子
假设你写了一个日期格式化工具包。
目录可能是这样:
src/
index.ts
package.json
tsconfig.json
源码:
import dayjs from 'dayjs'
export function formatDate(date: string): string {
return dayjs(date).format('YYYY-MM-DD')
}
你在开发时可能安装了这些包:
dayjstypescriptrollupvitesteslint
那该怎么分?
放到 dependencies
dayjs
因为真正运行 formatDate 时要用它。
放到 devDependencies
typescriptrollupvitesteslint
因为它们只是帮助你:
- 写 TypeScript
- 打包代码
- 测试功能
- 检查规范
用户运行 formatDate 本身,不需要这些工具参与。
十一、一个典型的 package.json 示例
{
"name": "demo-utils",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"dependencies": {
"dayjs": "^1.11.0"
},
"devDependencies": {
"typescript": "^5.0.0",
"rollup": "^4.0.0",
"eslint": "^9.0.0",
"vitest": "^1.0.0"
}
}
看到这个配置,可以这样理解:
dayjs是成品运行时要吃的“食材”typescript、rollup、eslint、vitest是制作和检查过程中的“工具”
十二、再说几个常见包,方便形成直觉
常见的 dependencies
这类包通常会直接出现在业务代码执行路径里:
axioslodashdayjsuuidreact(很多应用项目里是这样)vue(很多应用项目里是这样)
常见的 devDependencies
这类包通常服务于开发流程:
typescripteslintprettierjestvitestwebpackviterollupbabelsass(很多情况下)- 各类构建插件、测试插件、lint 插件
十三、一个很实用的口诀
可以记一句很接地气的话:
跟着产物跑的,放
dependencies;
只陪你开发的,放devDependencies。
或者再换一种说法:
用户运行时要用的,是
dependencies;
你写代码时要用的工具,是devDependencies。
十四、容易踩坑的地方
坑 1:把运行依赖误放进 devDependencies
结果可能是:
- 本地开发一切正常
- 发布后别人安装使用时报错
- 因为真正运行时缺依赖
这就像你做了一碗面,结果把“面”放进了工具箱,而不是放进食材清单里。
坑 2:把开发工具误放进 dependencies
结果通常不会立刻炸,但会带来一些问题:
- 安装体积变大
- 用户下载了不必要的包
- 依赖树更复杂
- 包管理更混乱
这就像你卖一碗面,还强行把菜刀、案板、烤箱一起塞给顾客。
十五、最后总结
把这件事说到最本质,其实就一句话:
dependencies是项目运行时真正要依赖的库;
devDependencies是项目开发、测试、构建、打包时使用的工具依赖。
判断时别问:
我开发时有没有用到它?
而要问:
用户真正运行这份代码时,还需不需要它?
需要,就是 dependencies。
不需要,就是 devDependencies。
十六、结尾
刚学 npm 时,dependencies 和 devDependencies 看起来只是两个字段的区别;
但理解透了之后,你会发现它本质上是在帮你区分:
- 哪些是“产品的一部分”
- 哪些是“生产产品的工具”
这个边界一旦建立起来,很多工程化问题都会顺。
因为写代码这件事,说到底也很像开店:
食材要分清,工具也要分清。
不然厨房会乱,顾客也吃不好。
如果喜欢这篇文章,请给我点个赞:)
祝大家:码上有钱 --Larry