Skip to content

02 · 前端工程化:Vite / Monorepo / 微前端 ​

本篇解决什么问题: 这三个是前端工程化里最容易被过度使用的东西——很多团队上了 Monorepo 和微前端, 结果复杂度暴涨、收益却没兑现。本篇给你**判断"该不该上"**的框架,而不是教你装工具。

⚠️ 易变信息说明:Vite / Turborepo / qiankun 的版本号、配置字段、命令变化很快, 按本库约定(正文写原理与判断框架)不写死版本与配置细节,请以官方文档为准。 本篇讲为什么快、解决什么问题、什么时候不该用——这些是长期稳定的。

📌 与 flutter/ 区的呼应(本区核心写法):

前端Flutter / Dart 对应参考
npm / pnpmpubFlutter 30 篇
Monorepo(pnpm workspace + Turborepo)melos / pub workspace本篇第二节
微前端(运行时集成)Add-to-App(运行时集成)Flutter 26 篇
构建产物分析与优化flutter build --analyze-sizeFlutter 23 篇

开篇:前端概念对照 ​

前端Flutter / Dart说明
Viteflutter build(但目标不同,见下)构建工具
HMR(热更新模块)Hot Reload改代码即时见效果
Webpack—(Flutter 无对应)前端特有的打包器
npm / pnpm / yarnpub包管理器
node_modules/.dart_tool/依赖目录
Monorepo(pnpm workspace)melos多包仓库管理
Turborepo / Nx—(melos 自带部分能力)任务编排 + 缓存
微前端Add-to-App运行时集成独立模块
Module Federation—(Flutter 无对应)Webpack 5 的运行时共享模块
Tree ShakingDart 的 tree shaking摇掉未用代码
Code SplittingDeferred components(延迟加载)按需加载

一个关键差异(前端转 Flutter 时最困惑的点):

前端Flutter
产物JS Bundle(可动态下发)机器码 / AOT(iOS 禁止下发)
代码分割常规操作(import())受限(deferred components,Android 为主)
运行时集成微前端方案成熟Add-to-App(原生工程集成)

这就是为什么微前端在前端可行、在 Flutter 里形态完全不同—— 本质是产物能否动态加载。详见 01 篇「热更新」一节。


一、Vite:为什么快,以及它解决什么问题 ​

1.1 核心原理(两个关键设计) ​

① 开发环境:利用浏览器原生 ESM,按需编译

Webpack 的思路(先打包再服务):
  改一个文件 → 重新打包整个依赖图 → 浏览器加载 bundle
  └ 项目越大越慢(几分钟甚至十几分钟)

Vite 的思路(按需编译):
  浏览器请求哪个模块 → 服务端才编译哪个模块 → 返回
  └ 启动速度与项目大小**基本无关**

② 依赖预构建(Pre-bundling)

node_modules 里的第三方库(CommonJS / 大量小文件)
  → 用 esbuild 预打包成少数几个 ESM 文件
  → 避免浏览器发几百个请求

esbuild 用 Go 编写,比 JS 编写的打包器快 10~100 倍

1.2 三个阶段的策略差异(重要) ​

阶段Vite 做什么为什么
开发(dev)原生 ESM + 按需编译 + esbuild 预构建追求启动快、HMR 快
生产(build)Rollup 打包追求产物质量(tree-shaking、代码分割更成熟)
预构建esbuild追求速度

💡 常见疑问:"为什么生产不用 esbuild?" 因为生产更看重产物体积与兼容性,Rollup 的 tree-shaking 和代码分割更成熟。 这是不同目标下选择不同工具的典型例子——不是"哪个工具更好"。

1.3 HMR 为什么快 ​

Webpack HMRVite HMR
机制需要重新构建受影响的部分模块链基于 ESM,精确到单个模块
速度随项目增大而变慢基本恒定,与项目大小无关

实际体感:大项目里 Webpack HMR 可能要几秒,Vite 通常几十毫秒。

1.4 什么时候 Vite 不是最优解 ​

要客观——Vite 不是万能的:

场景说明
需要兼容非常旧的浏览器Vite 开发环境依赖原生 ESM;生产虽可配 legacy 插件,但复杂
已有大型 Webpack 项目迁移成本可能大于收益(尤其重度依赖 Webpack 特有 loader/插件)
强依赖 Webpack 生态特定能力如 Module Federation(Vite 有替代方案,但非官方主流)

判断:新项目默认选 Vite;老项目评估迁移成本,不要为了"先进"而迁。

1.5 与 Flutter 的构建对比(认知校准) ​

Vite(前端)Flutter
开发时HMR(模块级替换)Hot Reload(亚秒级,保持状态)
生产构建Rollup 打包 JS/CSSAOT 编译为机器码
构建速度秒级~分钟级分钟级(AOT 编译较慢)
产物大小关注是(首屏体积)是(安装包体积,更敏感)

Flutter 的 Hot Reload 体验优于前端 HMR(能保持应用状态), 但生产构建慢于 Vite。这是 AOT 编译的必然代价。


二、Monorepo:什么时候该上 ​

2.1 先明确:Monorepo 解决什么问题 ​

Monorepo = 把多个包/应用放在同一个代码仓库里管理。

它解决的真实问题:

问题Monorepo 的解法
多个包之间改一个要发一次版才能联调workspace: 协议直接链接,改完立即生效
跨包的重构很难做(要跨仓库改)原子提交,一次改完
版本不一致(A 用 utils@1,B 用 utils@2)统一版本策略
重复配置(lint、tsconfig、CI)共享配置

2.2 它不解决什么(常见误解) ​

❌ 误解正解
"Monorepo 能让代码更清晰"❌ 它只解决协作与依赖管理,代码清不清晰靠边界设计
"Monorepo 能提升构建速度"❌ 单包时反而可能更慢;提速靠 Turborepo/Nx 的缓存
"上了 Monorepo 就等于解耦"❌ 反而更容易写出跨包耦合,因为"引用太方便了"

⚠️ 最大的风险:Monorepo 让跨包 import 变得太容易, 边界一旦失守,就会退化成"一个巨大的泥球 + 一堆目录"。

2.3 工具分层(别搞混) ​

层工具职责
依赖管理pnpm workspace / yarn workspace / npm workspace把本地包链接起来
任务编排 + 缓存Turborepo / Nx只跑受影响的任务、缓存构建结果
版本与发布Changesets自动改版本号、生成 CHANGELOG、发布

它们不是竞争关系,而是分层协作**:

pnpm workspace   → 解决"包怎么互相引用"
Turborepo        → 解决"任务怎么跑得快"
Changesets       → 解决"版本怎么发"

2.4 依赖管理的三种引用方式 ​

json
// ① workspace 协议(推荐):始终用本地最新版本
"dependencies": { "@my/utils": "workspace:*" }

// ② 版本范围:像普通依赖一样解析
"dependencies": { "@my/utils": "^1.0.0" }

// ③ 发布时:workspace:* 会被自动替换为真实版本号

💡 这正好对应 Flutter 30 篇 里的 path: 依赖——同样是"本地联调、发布时替换"的思路。 区别是前端用 workspace:* 由工具自动处理,Flutter 需要手动改成版本依赖。

2.5 决策:该不该上 Monorepo ​

问自己三个问题:

Q1: 有几个需要互相引用的包?
   ├─ 1~2 个,且不需要互相引用 ──→ ❌ 不需要 Monorepo,单仓库就行
   └─ 3 个以上,或需要互相引用 ──→ 继续 Q2

Q2: 是否频繁需要"改 A 包,立即在 B 包看到效果"?
   ├─ 否(各包独立演进)──→ ⚠️ 可以不上,多仓库 + 正常发版即可
   └─ 是(联调频繁)──→ 继续 Q3

Q3: 团队是否愿意维护这套工具链?
   ├─ 否 ──→ ❌ 别上。Monorepo 的复杂度是持续的,不是一次性的
   └─ 是 ──→ ✅ 上,但建议从 pnpm workspace 起步,
             等真的需要缓存再加 Turborepo

渐进路径(推荐):

阶段 1:pnpm workspace(成本低,解决 80% 的问题)
阶段 2:+ Turborepo(构建变慢时才加,解决缓存与任务编排)
阶段 3:+ Changesets(需要对外发版时才加)

2.6 与 Flutter 的对照 ​

前端Flutter / Dart
pnpm workspacemelos(Flutter 社区主流)
Turborepo 缓存melos 的 exec + 脚本(缓存能力弱于 Turborepo)
Monorepo 一个仓库多包Flutter 也可用 melos 管理多 package

本项目(HaierEnergy)目前是单仓库单 App(1100+ 个 Dart 文件在一个 lib/ 下), 尚未拆包。如要拆分,参考 30 篇 的 "什么时候不该抽"——先写重复,第 3 次再抽。


三、微前端:最容易被过度使用的方案 ​

3.1 是什么 ​

把多个独立开发、独立部署的前端应用,组合成一个整体。

主应用(壳)  ──┬── 子应用 A(React,团队 1,独立部署)
               ├── 子应用 B(Vue,团队 2,独立部署)
               └── 子应用 C(老 jQuery 系统,渐进迁移)

3.2 三种实现方案对比 ​

方案原理优点缺点
iframe浏览器原生隔离✅ 隔离最彻底、零成本❌ 路由/通信/样式/弹窗都难处理,体验割裂
qiankun(single-spa)监听路由,动态加载子应用 HTML/JS✅ 成熟、技术栈无关⚠️ JS 沙箱与样式隔离需额外处理
Module Federation(Webpack 5)运行时共享模块,可动态加载远程模块✅ 依赖可共享、体积更优⚠️ 绑定 Webpack、版本协调复杂

简单判断:

场景推荐
只是要嵌一个独立的第三方页面iframe(够用且最稳)
多团队技术栈异构、需独立部署qiankun
都是 Webpack 技术栈、想共享依赖Module Federation

3.3 什么时候真的需要微前端 ​

只有以下正当理由:

✅ 正当理由说明
多团队独立交付团队间发布节奏完全不同,不能相互阻塞
老系统渐进迁移老 jQuery/AngularJS 系统要逐步替换,不能停机重写
技术栈必须异构不同子应用必须用不同框架(罕见)
独立部署是硬需求合规或流程要求某模块能单独发版

3.4 什么时候不要用微前端(重点) ​

绝大多数项目不需要微前端。 下面是反信号:

❌ 反信号为什么不该用
"我们的项目很大"❌ 大 ≠ 需要微前端。单体 + 代码分割通常就够了
"想让代码更解耦"❌ 用模块边界和 lint 规则就行,运行时隔离是过度设计
"以后可能会用到"❌ YAGNI。等真需要时再拆,比提前拆容易
"团队之间要独立"⚠️ 先问:是不是协作流程问题?改流程可能比改架构有效
团队 < 3 个❌ 微前端的收益来自"多团队独立交付",人少时纯增加负担

💡 一句话判断: 微前端解决的是"组织问题"(多团队交付),不是"技术问题"(代码解耦)。 如果你是为了代码解耦,那就用错了工具。

3.5 微前端的真实代价(必须知道) ​

代价说明
样式隔离子应用 CSS 互相污染(需 Shadow DOM / CSS Modules / 前缀约定)
JS 沙箱全局变量、window 污染(qiankun 有沙箱但非完美)
依赖重复子应用各打包一份 React → 体积翻倍(MF 可缓解)
通信复杂跨应用状态共享要自己设计(事件总线 / 全局 store)
调试困难报错栈跨应用,本地要起多个服务
测试复杂集成测试要同时拉起多个应用

⚠️ 这些代价是持续的,不是"配置一次就完事"。

3.6 与 Flutter 的对照:为什么形态完全不同 ​

前端微前端Flutter
能否运行时动态加载✅ 可以(JS Bundle 可下发)❌ iOS 禁止下发可执行代码
主流的"模块化"运行时集成(微前端)编译期模块化(package / feature 目录)
真正的运行时集成—Add-to-App(原生工程嵌入 Flutter)
渐进迁移✅ 微前端成熟✅ Add-to-App(见 26 篇)

核心差异一句话: 前端能微前端,是因为 JS 可以动态执行; Flutter 只能在编译期或**原生层(Add-to-App)**做集成,因为 iOS 不允许下发代码。

这也解释了为什么 Flutter 社区没有"微前端"这个概念—— 需求被"feature 模块化 + Add-to-App"覆盖了。


四、工程化决策总表 ​

把三个主题放在一起,按"该不该上"总结:

技术默认该用吗触发条件最大风险
Vite✅ 新项目默认选新项目;老项目评估迁移成本老浏览器兼容、Webpack 生态依赖
Monorepo⚠️ 看情况≥3 个包且需频繁联调边界失守,退化成大泥球
微前端❌ 默认不用多团队独立交付 / 老系统渐进迁移复杂度暴涨,收益不兑现

一句话口诀:

Vite 大胆用,Monorepo 看需求,微前端别轻易碰。


五、构建优化的通用思路(跨框架适用) ​

不论用什么工具,优化思路是相通的:

手段作用前端Flutter 对应
Code Splitting按需加载,减小首屏体积import() 动态导入deferred components
Tree Shaking去掉未使用代码Rollup 自动Dart 编译自动
压缩减小体积esbuild / terser编译优化
缓存命中缓存不重复构建Turborepo 缓存CI 缓存 pub cache
产物分析找出体积大头rollup-plugin-visualizerflutter build --analyze-size
图片/字体优化souvent 是大头WebP、子集化字体子集、资源压缩

优化的正确顺序(别跳步):

1. 先测量(产物分析,找出真正的大头)   ← 90% 的人跳过这步
2. 再针对大头优化(通常是图片/字体/某个巨型依赖)
3. 最后才考虑代码分割等结构性优化

⚠️ 不要凭感觉优化。先测量,否则可能优化了个只占 2% 的东西。


六、自检清单 ​

学完本文,你应该能够:

  • [ ] 解释 Vite 在开发环境为什么快(原生 ESM 按需编译 + esbuild 预构建)
  • [ ] 说明为什么生产构建用 Rollup 而非 esbuild(目标不同)
  • [ ] 说出 Vite 在哪些场景下不是最优解
  • [ ] 区分 Monorepo 的三层工具(workspace / Turborepo / Changesets)各自职责
  • [ ] 说出 Monorepo 解决什么、不解决什么(至少各 2 条)
  • [ ] 说明 Monorepo 的最大风险(边界失守)
  • [ ] 用 2.5 的三个问题判断该不该上 Monorepo
  • [ ] 说出微前端的正当理由与反信号(至少各 3 条)
  • [ ] 解释"微前端解决组织问题而非技术问题"
  • [ ] 列出微前端的至少 4 项持续代价
  • [ ] 说明为什么 Flutter 没有"微前端"(iOS 禁止下发代码)
  • [ ] 说出构建优化的正确顺序(先测量再优化)
  • [ ] 记住口诀:"Vite 大胆用,Monorepo 看需求,微前端别轻易碰"

七、常见误区 ​

误区正解
"Vite 全面优于 Webpack"❌ 各有适用场景。老项目迁移要评估成本,Webpack 生态仍有不可替代能力
"Vite 开发和生产用同一套打包"❌ 开发用 ESM 按需编译,生产用 Rollup 打包
"Monorepo 能让代码更解耦"❌ 它只管协作与依赖管理;边界靠设计,甚至更容易写出耦合
"上了 Monorepo 就等于模块化"❌ 目录拆分 ≠ 模块解耦,跨包随便 import 会退化成泥球
"微前端很先进,我们的项目也该上"❌ 默认不该上。它是为"多团队独立交付"设计的
"项目大了就该用微前端"❌ 大 ≠ 需要微前端。单体 + 代码分割通常足够
"微前端能提升性能"❌ 通常会降低(依赖重复、多份运行时)。它是为交付效率,不是性能
"Flutter 也能做微前端"❌ 形态完全不同。iOS 禁止下发代码,只能编译期模块化或 Add-to-App
"优化就是加代码分割"❌ 先测量。可能大头是图片或某个巨型依赖
"构建工具越新越好"❌ 稳定性、团队熟悉度、生态兼容性往往更重要(同 Flutter 27 篇 的选型思路)

八、动手练习 ​

练习 A:给一个场景做判断(推荐) ​

"公司有一个中后台系统,React 技术栈,3 个前端团队共 12 人, 各自负责不同业务模块。目前都在同一个仓库、同一个应用里开发, 每次发版要等所有团队的功能都ready,经常互相阻塞。构建要 8 分钟。"

请判断:

  1. 该上 Monorepo 吗?为什么?
  2. 该上 微前端 吗?用 3.3 / 3.4 的清单逐条对照
  3. 如果不做微前端,"互相阻塞"的问题还能怎么解决?(提示:可能是流程问题)
  4. 构建 8 分钟,优先做什么?(提示:先测量)

练习 B:对照表填空 ​

把本篇的"前端 ↔ Flutter"对照表补充完整—— 结合你做过的两端项目,各补 3 条本篇没写的对照项。

💡 这张表补完,就是你这个知识库最独特的资产之一。

练习 C:审计一个真实项目 ​

挑你做过的一个前端项目,回答:

  1. 构建产物里,体积最大的三项是什么?(用产物分析工具测,别猜)
  2. 依赖里有没有本可以去掉的巨型库?(如引了整个 lodash 只用 3 个函数)
  3. 如果用 Monorepo,收益具体是什么?能量化吗?

相关文档 ​

本区 ​

Flutter 区(双向对照) ​