理解 devDependencies:它们真的不会被打包进生产代码吗?
在前端开发中,很多开发者都有一个常见误解:package.json
中的 devDependencies
是开发时依赖,因此不会被打包到最终的生产环境代码中。这个理解在一定条件下成立,但在真实项目中,打包工具(如 Vite、Webpack 等)并不会根据 devDependencies 或 dependencies 的位置来决定是否将依赖打包到最终的 bundle 中,而是完全俗义于代码中是否引用了这些模块。
本文将通过一个实际例子来说明这个问题,并提出一些实践建议来避免误用。
dependencies vs devDependencies 回顾
在 package.json
中,我们通常会看到两个依赖字段:
{
"dependencies": {
"lodash": "^4.17.21"
},
"devDependencies": {
"vite": "^5.0.0"
}
}
2
3
4
5
6
7
8
dependencies
:运行时依赖,通常用于项目在生产环境中运行所需的库。devDependencies
:开发时依赖,通常用于构建、测试、打包等过程,比如Babel
、ESLint
、Vite
等。
很多人认为把某个库放到 devDependencies
中就意味着它不会被打包进最终代码,但这只是约定俗成,并非构建工具的实际行为。
一个实际例子:lodash
被错误地放入 devDependencies
我们以一个使用 Vite 构建的库包为例:
目录结构
my-lib/
├── src/
│ └── index.ts
├── package.json
├── vite.config.ts
└── tsconfig.json
2
3
4
5
6
src/index.ts
import _ from 'lodash';
export function capitalizeName(name: string) {
return _.capitalize(name);
}
2
3
4
5
错误的 package.json
{
"name": "my-lib",
"version": "1.0.0",
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"scripts": {
"build": "vite build"
},
"devDependencies": {
"vite": "^5.0.0",
"lodash": "^4.17.21",
"typescript": "^5.4.0"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
注意:lodash
被放到了 devDependencies
中,而不是 dependencies
中。
构建后结果
执行 npm run build
后,你会发现 lodash
的代码被打包进了最终输出的 bundle
中,尽管它被标记为 devDependencies
。
dist/
├── index.js ← 包含 lodash 的代码
├── index.mjs
└── index.d.ts
2
3
4
为什么会发生这种情况?
构建工具(如 Vite
、Webpack
)在处理打包时,并不会关心某个依赖是 dependencies
还是 devDependencies
。
它只会扫描你的代码:
- 如果你
import
了某个模块(如lodash
),构建工具会把它包含进bundle
中,除非你通过external
配置显式告诉它不要打包进来。 - 你放在
devDependencies
中只是告诉npm install
:这个依赖只在开发阶段需要,npm install --production
时不会安装它。
换句话说,打包行为取决于代码,而不是依赖声明。
修复方式:将运行时依赖移到 dependencies
为了正确构建一个可以发布的库包,应该:
{
"dependencies": {
"lodash": "^4.17.21"
},
"devDependencies": {
"vite": "^5.0.0",
"typescript": "^5.4.0"
}
}
2
3
4
5
6
7
8
9
这样使用你库的开发者才能在安装你的包时自动获取 lodash。
如何防止此类问题?
使用 peerDependencies(推荐给库开发者)
如果你希望使用者自带 lodash,而不是你来打包它,可以这样配置:
{
"peerDependencies": {
"lodash": "^4.17.21"
}
}
2
3
4
5
同时在 Vite 配置中加上:
export default defineConfig({
build: {
lib: {
entry: 'src/index.ts',
name: 'MyLib'
},
rollupOptions: {
external: ['lodash'], // 不打包 lodash
}
}
})
2
3
4
5
6
7
8
9
10
11
这样打包出来的 bundle 中就不会再包含 lodash 的代码。
使用构建工具的 external 配置
像上面这样将 lodash
标为 external
可以避免误打包。
静态分析工具检测
使用像 depcheck
或 eslint-plugin-import
等工具,可以帮你发现未声明或声明错误的依赖。
总结
依赖位置 | 作用说明 | 备注 |
---|---|---|
dependencies | 生产环境运行时必须使用的库 | |
devDependencies | 开发、构建过程所需的工具库 | |
peerDependencies | 你的库需要,但由使用者提供的依赖(库开发推荐) |
- 所有运行时依赖都放在
dependencies
或peerDependencies
; - 构建工具正确配置
external
,避免不必要地打包外部依赖; - 使用工具检查依赖定义的一致性。