迟来的“打脸”
一年前,我在这台电脑前敲下了一篇关于 Node.js 依赖管理的博客。在那篇文章里,我信誓旦旦地写道:
“如果你把 vue-cli-service 放到了 devDependencies 下,那么在你执行 npm run build 的时候,迎接你的就是无情的报错了,所以打包相关的依赖,在这种应用场景下,是不能放到 devDependencies 下的。”
一年后的今天,当我重新审视这段文字,我不禁老脸一红。作为一个时常与 Webpack、Vite 和 CI/CD 打交道的开发者,我必须站出来对过去的自己进行一次“拨乱反正”。 趁机也聊一下前端工程化中一个核心却容易被忽视的概念:环境的边界。
核心误区的起底:谁才是真正的“生产环境”?
我当年的逻辑推导是:npm install --production 不会安装 devDependencies -> 打包需要 vue-cli-service -> 所以它必须在 dependencies 里。 这个逻辑在纯 Node.js 服务端项目(如 Express 接口服务)中是也许没错,但在前端网页应用中却南辕北辙了。原因在于我混淆了两个概念:
- Node.js 运行时环境: 你的代码直接跑在服务器的 CPU 和内存里。
- 前端构建环境: 你的代码(Less, TS, Vue/React)只是“原材料”,通过工具加工成静态的 JS/CSS。
结论: 对于前端项目,vue-cli-service 的生命周期在 npm run build 结束的那一刻就彻底终结了。它永远不会出现在用户的浏览器里,因此它是不折不扣的“开发时依赖”。
拨乱反正
- 为什么当年我觉得“不放 dependencies 就会报错”?
在传统的运维思维里,大家习惯在生产服务器上拉取源码,然后地执行:
npm install --production npm run build
在这种极度简陋的流水线下,确实会报错。但这种做法在现代工程化中是不可取的。
- 现代 CI/CD 的标准姿势
在目前的自动化构建流(如 GitHub Actions, Jenkins, Vercel)中,流程是:
- 构建阶段(Build Phase): 执行 npm install(全量安装),然后 npm run build 产出静态文件。
- 部署阶段(Deployment Phase): 仅仅将打包好的 dist 目录移动到 Nginx 或 CDN。
在这个模型下,生产环境压根不需要 node_modules,更不需要 vue-cli-service。
依赖分区指南
为了不再犯之前的错误,我们可以用两个简单的问题来做判别:
| 判别问题 | 答案为“是” | 存放位置 | 举例 |
|---|---|---|---|
| 代码会进入最终的 Bundle 吗? | 是 | dependencies | React, Axios, Dayjs |
| 代码只在打包、测试或校验时有用吗? | 是 | devDependencies | Less-loader, ESLint, Vite |
特殊说明: 只有像 Next.js 或 Nuxt.js 这种需要 SSR(服务端渲染) 的框架,情况会稍微复杂。因为服务器在运行时确实需要运行 React/Vue 的代码,但即便如此,less-loader 依然属于开发依赖,因为样式在构建阶段就已经编译完成了 —— 来自 2025年的补充说明。
结语
正确的依赖分区能带来更小的镜像体积、更短的构建时间以及更清晰的项目结构,搞明白还是很重要的。