← 返回题目列表

dependencies、devDependencies 和 peerDependencies 有什么区别?

高频 中等 第 8 / 31 题 更新于 2026/07/29
前端工程化npm依赖管理package.json

简化版

dependencies 是运行时需要的依赖,devDependencies 是开发、构建、测试阶段需要的依赖,peerDependencies 表示当前包要求使用方提供某个宿主依赖,常见于 React 组件库、Webpack 插件、Babel 插件等。

详细版

应用项目里,React、axios 这类运行时代码会进 dependencies;TypeScript、ESLint、Vite、测试工具通常放 devDependencies。库项目要更谨慎:如果把 React 打进组件库的 dependencies,使用方可能加载两份 React,因此通常把 React 放到 peerDependencies,再在本库开发时放一份到 devDependencies

peerDependencies 的含义不是“我会安装它”,而是“我需要宿主环境有一个兼容版本”。npm 7 以后会尝试自动安装 peer,但工程上仍要理解它表达的是兼容契约。

回答时最好结合包类型:应用、库、插件的依赖策略不同,不能机械背字段定义。

完整版教学

一、依赖字段本质是在描述包的运行边界

package.json 不是简单的清单,它描述当前包在不同阶段需要什么。应用项目、组件库、构建插件面对的“运行环境”不同,所以依赖字段也不同。

应用最终会部署给用户,运行时代码需要的包必须能进入产物或运行环境。库会被别人安装,它要避免把宿主已经存在的框架重复带进去。插件则依赖某个宿主工具,比如 Webpack、Vite、Babel 或 ESLint。

记忆钩子:dependencies 管自己运行,devDependencies 管自己开发,peerDependencies 管和宿主的兼容关系。

二、dependencies 放运行时真正需要的包

如果你的业务代码在浏览器运行时调用 axios.get(),那么 axios 是运行时依赖。构建产物虽然可能把 axios 打进 bundle,但从语义上它仍是应用运行需要的一部分。

{
  "dependencies": {
    "axios": "^1.7.0",
    "react": "^18.2.0"
  }
}

对 Node 服务也是一样,Express、数据库驱动、日志库如果线上启动需要,就属于 dependencies。判断标准不是“会不会被打包”,而是“没有它,线上功能能不能跑”。

三、devDependencies 放开发构建阶段需要的包

devDependencies 通常包括编译器、测试工具、格式化工具、类型包、构建工具等。它们服务于开发和交付流程,不应该成为业务运行时的必要条件。

依赖常见字段原因
typescriptdevDependencies编译检查工具
eslintdevDependencies代码质量工具
vitedevDependencies本地开发和构建工具
vitestdevDependencies测试工具
@types/reactdevDependencies类型声明

注意,前端应用生产构建时仍会安装 devDependencies,因为构建步骤需要它们。不能说“生产环境一定不安装 devDependencies”,这取决于部署流程是先构建再只发布产物,还是服务器上现场构建。

四、peerDependencies 表达宿主依赖契约

组件库最典型。一个 React 组件库不应该强行带一份自己的 React,否则应用里可能出现两份 React,导致上下文、hooks 或渲染器行为异常。因此组件库会声明自己需要使用方提供 React。

{
  "peerDependencies": {
    "react": ">=18 <20",
    "react-dom": ">=18 <20"
  },
  "devDependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

这表示:开发组件库时本地需要 React,所以放 devDependencies;发布给用户时要求宿主应用提供兼容 React,所以放 peerDependencies

五、插件类包为什么经常使用 peerDependencies

Webpack loader、Babel 插件、ESLint 插件、Vite 插件都运行在宿主工具体系内。插件通常要和宿主工具的 API 版本匹配,因此会用 peer 说明兼容范围。

例如一个 ESLint 插件可能要求 eslint >=8。如果它自己带一份 ESLint,就可能和用户项目实际运行的 ESLint 不是同一个实例,规则加载和配置解析会出问题。

用户项目 eslint
  -> 加载 eslint-plugin-xxx
       -> 插件声明 peerDependencies: eslint >= 8

peer 的价值是把版本冲突暴露给安装阶段,而不是等到运行时报一个模糊的插件 API 错误。

六、optionalDependencies 和 overrides 也常被追问

optionalDependencies 表示依赖安装失败也不一定让整个安装失败,常用于跨平台原生包或可选能力。比如某些平台专用二进制包,只在对应系统上需要。

overrides 用于在根项目强制覆盖传递依赖版本,常见场景是修补安全漏洞或统一依赖版本。它很有用,但也可能打破上游包的兼容假设,所以需要配合测试。

字段核心含义常见场景
optionalDependencies可选安装平台相关能力
overrides根项目覆盖传递依赖安全漏洞修补、版本统一
peerDependenciesMeta标记 peer 可选插件支持多个宿主

七、应用项目和库项目的策略不同

应用项目以可运行为目标,依赖更偏“我自己要用什么”。库项目以被集成为目标,依赖更偏“哪些应该由使用方决定”。这就是很多人把库项目依赖写错的原因。

假设组件库把 React 放进 dependencies,应用本身也装了 React。打包配置如果没有 external 掉 React,用户可能多下载 40 KB 以上的重复代码;更严重的是运行时可能出现两个 React 实例。

库项目常见策略是:框架放 peer,构建工具放 dev,小型纯函数运行依赖可以放 dependencies,发布构建时把 peer external 掉。

八、常见误区与追问

  • 误区:devDependencies 线上一定不会被安装。 如果线上环境负责构建,就仍可能安装;区别在语义和部署阶段。
  • 误区:peerDependencies 是开发依赖。 peer 表达宿主兼容契约,开发本地通常还要配一份 devDependencies。
  • 误区:所有库依赖都应该放 peer。 只有需要宿主共享或版本协同的依赖适合 peer,小型内部工具依赖可以放 dependencies。
  • 追问:React 组件库为什么把 React 放 peer? 为了避免多 React 实例,并让宿主应用控制框架版本。
  • 追问:overrides 适合解决什么问题? 适合根项目临时统一或修补传递依赖版本,但需要测试验证兼容。
  • 追问:怎么判断依赖放哪里? 看它是运行时必须、开发构建必须,还是需要使用方提供的宿主依赖。

九、加强记忆

把依赖字段记成“三个边界”:运行边界用 dependencies,开发边界用 devDependencies,宿主兼容边界用 peerDependencies。应用项目关注自己能跑,库和插件关注别人集成时不重复、不冲突、版本可协商;回答时带 React 组件库或 ESLint 插件例子最稳。