npm依赖和devDependencies到底怎么区分

文章导读
区分的判断标准只有一条:这段代码在应用启动、运行或作为产物交付时是否真的被加载。凡是生产运行时需要用到的包(框架、运行时库、数据库驱动、对外导出的类型依赖)放进 dependencies;只在开发、测试、构建、类型检查、lint 阶段用到的包(编译器、打包器、测试框架、@types 私有类型包、格式化工具)放进 devDependencies。判断不了时,先按“生产环境少装一个包会报错吗”这条线做
📋 目录
  1. A 先确认当前场景和目标
  2. B 按这个顺序处理
  3. C 改完后怎么验证
  4. D 容易忽略的边界
A A

区分的判断标准只有一条:这段代码在应用启动、运行或作为产物交付时是否真的被加载。凡是生产运行时需要用到的包(框架、运行时库、数据库驱动、对外导出的类型依赖)放进 dependencies;只在开发、测试、构建、类型检查、lint 阶段用到的包(编译器、打包器、测试框架、@types 私有类型包、格式化工具)放进 devDependencies。判断不了时,先按“生产环境少装一个包会报错吗”这条线做初判,再用下面小节中的命令核对。

把包放进 dependencies 还是 devDependencies,取决于该包是否出现在生产运行时链路上:运行时代码 import 的、被服务端启动脚本加载的、对外发布包的类型声明依赖,通常放 dependencies;构建工具、测试框架、lint 与本地类型注解一般放 devDependencies。操作上先用 npm ls 和代码里的 import 对照,再在干净环境用 npm ci --omit=dev 安装并启动验证。边界是 peerDependencies、optionalDependencies 以及对外发布库的类型依赖,需要单独判断,不能一刀切。

先确认当前场景和目标

在动手改 package.json 之前,先确认三件事,否则很容易把能跑的工程改坏。

  • 这个包是应用还是被发布出去给别人的库。 应用型项目只要保证生产环境能启动即可;对外发布的库还要考虑使用方安装时是否需要这个包,以及导出的类型声明里是否引用了它,这两种场景结论可能相反。
  • 生产环境怎么装依赖。 如果部署脚本或镜像构建用的是 npm ci 或 npm install 不带裁剪参数,那两种依赖都会被装上,区分只影响安装体积和安装时长;如果用了 --omit=dev 或 --production,区分就直接决定服务能不能起来。
  • 运行时加载路径。 服务端在启动入口 require/import 的、被依赖注入配置加载的、被同构代码在浏览器端执行的,都算运行时链路;只在 scripts 里被 build、test、lint 调用的,基本属于开发链路。

这三条确认完,再决定是“整体迁移若干包”还是“只补充个别包的归属”,目标不同,处理顺序也不同。

npm依赖和devDependencies到底怎么区分

按这个顺序处理

  1. 先看清现状。 分别导出两份清单,避免凭印象改:
npm pkg get dependencies
npm pkg get devDependencies
npm ls --depth=0

npm ls 展示的是已经安装并实际解析到的依赖树,比 package.json 上写的更能反映当前状态;如果某个包出现在树里但不在两份清单中,说明它是被当作 peer 依赖或间接依赖带进来的,先不要动它。

  1. 用代码反查真实引用。 在源码目录里搜索包名,确认引用位置。只出现在测试文件、构建配置、类型声明文件里的,通常可以归到开发依赖;出现在启动入口、路由、数据访问层的,保留在 dependencies。
  2. 核对脚本。 打开 package.json 的 scripts,看 build、test、lint 用到的命令行工具是否本来就该在开发依赖里;很多误判是因为把打包器、CLI 工具直接 npm install 装进了 dependencies。
  3. 执行迁移。 归属明确的包用命令改,不要手改 JSON 后忘记同步 lock 文件:
npm install --save-dev some-build-tool
npm install --save-prod some-runtime-lib

每次改完确认 package.json 与 package-lock.json 都已更新,两者不同步时 CI 上的 npm ci 会直接失败。

npm依赖和devDependencies到底怎么区分
  1. 对拿不准的包做保守处理。 如果一个包只有在特定功能分支才会被加载,建议先留在 dependencies,确认该功能在生产中可达后再定,避免因为裁剪依赖导致线上某个路径才报错、排查成本高。

改完后怎么验证

验证要模拟“只有生产依赖”的环境,而不是在当前 node_modules 上直接跑,因为残留的包会掩盖问题。

  • 在干净的副本或临时目录里执行 npm ci --omit=dev,观察安装过程是否报错;较新的 npm 支持 --omit=dev,老版本可用 --production,需要结合团队实际版本确认。
  • 安装完成后跑 npm ls --omit=dev --depth=0,确认被迁走的包已经不在生产依赖列表里,仍在列表里的包都是预期内的运行时依赖。
  • 启动应用或执行生产构建产物,逐条走一遍关键链路:启动入口加载、数据库或外部服务连接、涉及被迁移包的那个功能路径。预期信号是启动无模块找不到错误、关键接口能正常响应。
  • 单独检查类型检查和构建:npm run build、npm test 应正常完成,说明开发依赖没有被误删。

如果验证只能在本地进行,把“生产环境用 --omit=dev 安装”这条写进部署脚本或 CI 配置,让后续每次发布都自动校验,而不是靠人工记。

容易忽略的边界

  • peerDependencies 不受这个划分影响。 插件、UI 组件库、eslint 配置包通常把宿主框架声明为 peer 依赖,交由使用方安装,不要顺手挪进 dependencies,否则容易出现同一宿主被装成多份。
  • optionalDependencies 的语义不同。 这类包允许安装失败而不中断整体安装,通常用于平台相关的二进制模块,把它当普通运行时依赖迁移会改掉容错行为。
  • 对外发布库的类型包要单独看。 如果导出的类型签名引用了某个 @types/*,使用方做类型检查时可能需要它;这类包是放 dependencies 还是 devDependencies,取决于发布方式和构建产物,需要结合项目实际确认,不能沿用应用项目的习惯。
  • monorepo 与私有源。 工作区内互相引用的包用 workspace 协议,不走 registry 语义;私有源的可用性和缓存策略也会影响安装结果,迁移依赖时优先在贴近真实网络条件的环境中验证一次。
  • 回退方式。 迁移后如果出现生产环境模块缺失,优先把对应包移回 dependencies 并重新生成 lock 文件,而不是在生产环境手工补装;紧急情况下可临时调整部署命令保留完整安装,但应尽快把依赖归属改正确,不要让完整安装长期作为默认方案。