前端工程化
从包管理、Monorepo、构建系统到 CI/CD,深入理解现代前端工程基础设施
前端工程化真正解决的不是:
“怎么把项目跑起来?”
而是:
当项目从一个人、一个应用、一个仓库,增长到几十人、多个应用、几十个共享包之后,如何继续保持可维护性?
因此工程化本质上解决的是:
- 复杂度
- 依赖关系
- 构建效率
- 发布效率
- 质量控制
- 可观测性
1. 从“项目”转向“系统”
一个简单项目可能只有:
src/
package.json大型项目会变成:
apps/
web/
admin/
docs/
packages/
ui/
utils/
http/
logger/
analytics/
config/此时最大的问题已经不是代码量,而是:
依赖关系。
2. Monorepo 本质是什么?
Monorepo 不是:
把很多项目放到一个 Git 仓库。
真正的 Monorepo 是:
多个独立 package 在统一依赖图和统一工程基础设施下协同开发。
例如:
apps/web
↓
packages/ui
↓
packages/theme同时:
apps/admin
↓
packages/ui于是形成:
┌── apps/web
│
packages/ui ────┤
│
└── apps/admin3. 最重要的是依赖方向
推荐:
apps
↓
domain
↓
shared
↓
foundation例如:
app
↓
business package
↓
UI package
↓
utils不能出现:
utils
↓
app否则形成反向依赖。
4. 为什么 Monorepo 最容易失败?
因为很多团队只做了:
统一仓库却没有做:
依赖治理例如:
package-a → package-b
package-b → package-c
package-c → package-a最后形成循环依赖。
因此大型 Monorepo 必须关注:
Dependency Graph而不是只关注目录结构。
5. Workspace 到底解决了什么?
现代 package manager 的 workspace 能让:
{
"dependencies": {
"@company/ui": "workspace:*"
}
}直接引用本地 package。
开发时:
apps/web
↓
workspace
↓
packages/ui而发布时:
packages/ui
↓
npm registry这让:
本地开发依赖和发布依赖保持一致。
6. Build System 的核心:依赖图
假设:
A → B → C如果 C 修改:
C changed
↓
B affected
↓
A affected但如果:
D和 C 没有关系:
D就不应该重新构建。
因此现代构建系统核心能力之一是:
增量构建。
7. Cache 的本质
构建缓存不是:
“把之前的结果存起来。”
而是:
Input
↓
Hash
↓
Cache Key
↓
Build Result如果:
InputHash === CacheHash那么:
Build
↓
Cache Hit否则:
Cache Miss
↓
重新构建因此一个好的构建系统必须尽可能让:
输入 → 输出变得确定。
8. 为什么 lockfile 非常重要?
假设:
"react": "^19.0.0"今天安装:
19.0.1下个月:
19.1.0如果没有 lockfile:
同一个 package.json
↓
不同依赖树CI 环境可能出现:
本地没问题,CI 出问题。
lockfile 的核心作用就是:
锁定依赖图。
9. CI/CD 不应该只是“执行 npm build”
成熟流水线应该是:
Git Push
↓
Lint
↓
Type Check
↓
Unit Test
↓
Build
↓
Artifact
↓
Deploy
↓
Smoke Test
↓
Observability10. Build 和 Deploy 应该分离
推荐:
Source
↓
Build
↓
Artifact
↓
Deploy而不是:
Server
↓
git pull
↓
npm install
↓
npm build
↓
deploy因为后者会导致:
生产环境重新编译从而降低发布确定性。
11. Artifact 是什么?
Artifact 就是:
构建之后真正需要部署的结果。
例如:
.next/
dist/
worker.js
static/
manifest.json发布系统应该尽可能做到:
Build Once
Deploy Many即:
同一个 artifact
↓
test
↓
staging
↓
production而不是每个环境重新构建一次。
12. 环境变量为什么经常出问题?
前端环境变量实际上存在不同生命周期:
Build Time
Runtime
Browser
Server例如:
NEXT_PUBLIC_API_URL=...可能在:
build time被注入 JavaScript bundle。
这意味着:
修改环境变量不一定意味着修改运行时配置。
因此大型前端项目应该明确区分:
Build Config
Runtime Config
Secret
Public Config13. 前端工程真正高级的方向:可观测构建
构建系统不仅应该告诉你:
Build Failed还应该知道:
Build Time
Package
Cache Hit Rate
Bundle Size
Chunk Count
Dependency Changes
Artifact Size最终可以形成:
Code
↓
Build
↓
Artifact
↓
Deploy
↓
Runtime
↓
Telemetry这才是完整的工程闭环。
14. 前端工程化的终点
成熟工程最终应该形成:
代码规范
↓
依赖治理
↓
构建系统
↓
测试体系
↓
CI/CD
↓
发布系统
↓
监控系统
↓
反馈代码质量所以:
工程化不是工具集合,而是研发系统。
15. 其他
为什么 Monorepo 适合大型前端?
因为它可以统一:
- 依赖
- 工具链
- CI
- 版本
- 代码复用
- 构建缓存
但它同时带来:
- 构建复杂度
- 依赖治理
- 权限边界
- CI 复杂度
所以 Monorepo 并不是天然更好,而是适合具有大量共享代码和统一研发基础设施的团队。
为什么要做增量构建?
因为大型仓库中:
一次代码修改不应该导致:
所有 package 全量构建核心是:
Dependency Graph
+
Content Hash
+
Cache为什么 Build Once Deploy Many?
因为这样可以减少:
环境差异让测试通过的 artifact 和生产环境运行的是同一个构建结果。