我的文档库

前端工程化

从包管理、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/admin

3. 最重要的是依赖方向

推荐:

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
   ↓
Observability

10. 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 Config

13. 前端工程真正高级的方向:可观测构建

构建系统不仅应该告诉你:

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 和生产环境运行的是同一个构建结果。

本页目录