Files
Situation-Awareness-Platfor…/docs/微前端架构MicroApps模块化开发实现详解.md
cfy666 c1e9a4be83 chore: sync local changes and add documentation
- Update yarn.lock
- Add project implementation docs in docs/
- Add personal internship experience notes in 实习讲解/
2026-06-29 19:47:30 +08:00

404 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 微前端架构 MicroApps 模块化开发 — 面试版
> 简历原话:**"采用微前端架构 MicroApps 支持模块化开发"**
>
> 这篇文档帮你理解这句话背后到底做了什么,以及面试时怎么讲。
---
## 一、先搞清楚:这句话到底是什么意思?
拆成三部分理解:
| 关键词 | 含义 | 项目中对应 |
|--------|------|------------|
| **微前端** | 把一个大前端应用拆成多个小应用,每个小应用独立开发、独立部署 | 主应用(态势感知平台)+ 3 个子应用首页、用户中心、AI Copilot |
| **MicroApps** | 具体使用的微前端框架名,全称 `@micro-zoe/micro-app`(京东开源) | package.json 中声明的依赖,版本 1.0.0-rc.3 |
| **模块化开发** | 每个子应用由不同团队/人员独立开发,互不影响 | 3 个子应用各有独立的代码仓库、独立的域名、独立的部署地址 |
**一句话概括**:我用京东开源的 micro-app 框架,把平台拆成主应用 + 3 个子应用,实现了各模块独立开发、独立部署,同时通过消息通信保证它们协同工作。
---
## 二、为什么需要微前端?(面试必答)
### 没有微前端的问题
想象一下没有微前端的情况:
```
一个巨大的 Vue 项目,包含:
├── 首页/搜索功能(首页团队负责)
├── 用户中心/个人主页(用户团队负责)
├── AI Copilot 对话AI 团队负责)
└── 态势感知/安全分析(你负责)
问题:
1. 所有人改同一个代码仓库,合并冲突不断
2. 一个模块出 bug整个平台挂掉
3. 改一个小功能,要重新部署整个项目
4. 各团队技术栈想用不同的,但被绑死在一个项目里
```
### 用了微前端之后
```
主应用(壳):负责顶部导航栏、侧边栏、路由调度
├── 子应用1首页团队的独立项目负责首页/搜索
├── 子应用2用户团队的独立项目负责用户中心
├── 子应用3AI团队的独立项目负责 AI Copilot
└── 你自己开发的模块:直接写在主应用里(安全态势/生态/社区)
好处:
1. 各团队独立仓库、独立开发,互不干扰
2. 子应用挂了不影响主应用和其他子应用
3. 子应用可以独立部署,不用动主应用
4. 各子应用可以用不同的技术栈(虽然我们都是 Vue3
```
---
## 三、项目里具体怎么用的?
### 3.1 整体架构图
```
┌─────────────────────────────────────────────────────┐
│ 主应用(你的项目) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │
│ │ 顶部导航栏 │ │ 侧边菜单栏 │ │ 内容区域(路由) │ │
│ └──────────┘ └──────────┘ │ │ │
│ │ /explore → 子应用1 │ │
│ │ /user → 子应用2 │ │
│ │ /ai → 子应用3 │ │
│ │ /security → 你开发的 │ │
│ └───────────────────┘ │
└─────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────┐ ┌──────────┐ ┌──────────────┐
│ 子应用1 │ │ 子应用2 │ │ 子应用3 │
│ 首页/搜索 │ │ 用户中心 │ │ AI Copilot │
│ (独立域名) │ │ (独立域名) │ │ (独立域名) │
└─────────────┘ └──────────┘ └──────────────┘
```
### 3.2 3 个子应用分别是什么
| 子应用名 | 干什么用 | 独立域名(生产环境) |
|----------|----------|---------------------|
| `micoro-app-homeweb-app` | 首页、搜索、G-Star 开源榜 | `homepage-app.gitcode.com` |
| `user-center` | 用户个人主页、仓库、收藏、设置 | `usercenter-app.gitcode.com` |
| `copilot-app` | AI 智能助手对话 | `copilot-app.gitcode.com` |
每个子应用都有**4 套环境地址**(开发/测试/预发/生产),通过 `.env` 文件配置:
```bash
# .env.development开发环境
VITE_CHILD_HOMEWEB_HOST = 'https://test.gitcode.net/child/homeweb-app/'
VITE_USER_CENTER_HOST = 'https://test.gitcode.net/child/user-center/'
VITE_COPILOT_HOST = 'https://test.gitcode.net/child/aichat-app/'
# .env.production生产环境
VITE_CHILD_HOMEWEB_HOST = 'https://homepage-app.gitcode.com'
VITE_USER_CENTER_HOST = 'https://usercenter-app.gitcode.com'
VITE_COPILOT_HOST = 'https://copilot-app.gitcode.com'
```
---
## 四、怎么实现的?(面试核心)
### 4.1 怎么把子应用"嵌入"主应用
有两种方式,项目里都用了:
#### 方式一:声明式 — `<micro-app>` 标签(用于用户中心)
就像写 HTML 标签一样简单:
```vue
<!-- proxy.vue 用户中心的代理组件 -->
<template>
<micro-app name="user-center" :url="url" iframe baseroute="/"></micro-app>
</template>
<script setup>
const url = import.meta.env.VITE_USER_CENTER_HOST; // 从环境变量取子应用地址
</script>
```
**意思是**:在页面上放一个 `<micro-app>` 标签,告诉它子应用叫 `user-center`,地址是 `url`,用 iframe 模式加载。就这么简单。
#### 方式二:命令式 — `microApp.renderApp()`(用于首页和 AI
用 JavaScript 手动调用渲染:
```typescript
// proxy-homeweb.vue — 首页子应用的代理组件
onMounted(() => {
microApp.renderApp({
name: 'micoro-app-homeweb-app', // 子应用名
url: origin, // 子应用地址
container: '#homeweb-container', // 渲染到哪个 DOM 元素
data: { emitter, $router: router }, // 传给子应用的数据
iframe: true, // 用 iframe 隔离
baseroute: '/', // 路由前缀
});
});
onBeforeUnmount(() => {
microApp.unmountApp('micoro-app-homeweb-app'); // 页面离开时卸载子应用
});
```
**为什么两种方式都要用**:声明式更简单,但命令式更灵活,可以在渲染时传入更多数据(如事件总线、路由实例)。
### 4.2 子应用怎么和主应用"对话"
微前端最难的部分不是加载子应用,而是**主应用和子应用之间的通信**。项目里用了 3 种通信方式:
#### 方式 1`setData` — 主应用主动发消息给子应用
```typescript
// 主应用告诉用户中心"路由变了"
microApp.setData('user-center', {
type: 'application-route',
name: route.name,
params: route.params,
query: route.query
});
// 主应用告诉首页子应用"搜索关键词变了"
microApp.setData('micoro-app-homeweb-app', {
type: 'keyword-change',
keyword: searchKeyword
});
```
#### 方式 2`addDataListener` — 主应用监听子应用的消息
```typescript
// 主应用监听用户中心发来的各种消息
microApp.addDataListener('user-center', (config) => {
// 子应用说"用户关注状态变了"
if (config.type === 'user_hasFollowed_update') {
otherStore.saveFollowed(config.data.status);
}
// 子应用说"用户资料更新了"
if (config.type === 'user_profile_update') {
saveAccountInfo(config.data);
}
// 子应用说"要跳转路由"
if (config.type === 'route-change') {
router.push({ name: config.name, params: config.params });
}
});
```
#### 方式 3共享事件总线 — 通过 mitt 事件库
```typescript
// 主应用创建事件总线,传给子应用
microApp.renderApp({
name: 'micoro-app-homeweb-app',
data: {
emitter, // ← mitt 事件总线实例
$router: router // ← 路由实例
}
});
// 子应用通过事件总线通知主应用"路由变了"
// 主应用监听这个事件,同步到自己的路由
addEventListener('microRouterChange', (to) => {
router.push(to.fullPath);
});
```
**通信方式总结**
```
主应用 → 子应用microApp.setData('子应用名', 数据)
子应用 → 主应用microApp.addDataListener('子应用名', 回调)
双向通信:共享 emitter 事件总线
```
### 4.3 路由怎么同步?
主应用和子应用各有各的路由,需要保持同步:
```typescript
// microAppConfig.ts — 路由同步工具
// 1. 子应用路由变了 → 同步到主应用
addEventListener('microRouterChange', (to) => {
router.push(to.fullPath); // 主应用跟着跳
});
// 2. 主应用路由变了 → 同步到子应用
router.beforeEach((to, from) => {
if (to.meta.micorApp) {
// 告诉子应用:你要跳到这个路径
to.meta.micorApp.forEach(appName => {
microApp.router.replace({ name: appName, path: to.fullPath });
});
}
});
```
**通俗理解**:就像两个人各开一辆车,要保持同一路线。主应用的路由变了,通知子应用跟上;子应用的路由变了,也通知主应用跟上。
### 4.4 页面加载优化
为了让子应用加载更快,做了两个优化:
#### 优化 1DNS 预解析
```html
<!-- index.html — 提前解析子应用域名的 DNS -->
<link rel="preconnect" href="//homepage-app.gitcode.com" crossorigin />
<link rel="dns-prefetch" href="//homepage-app.gitcode.com">
<link rel="dns-prefetch" href="//usercenter-app.gitcode.com">
```
**通俗理解**:用户还没点到子应用的页面,浏览器就已经提前"打听"子应用域名的地址了,等真正加载时就更快。
#### 优化 2开发环境跨域处理Vite proxy
子应用和主应用不在同一个域名下,但 micro-app 的 iframe 模式下,主子应用通信靠 `postMessage`(原生 API天然支持跨域不受 CORS 限制),所以**不需要额外配置 CORS**。
真正需要处理跨域的是**主应用调用后端 API**的场景。开发环境中,前端跑在 `localhost:443`,后端在 `http://222.20.126.217:8100`域名不同会触发跨域。Vite 用 proxy 代理解决:
```typescript
// vite.config.ts
server: {
proxy: {
'/api': {
target: 'http://222.20.126.217:8100', // 后端地址
changeOrigin: true, // 修改 Origin 头,绕过同源策略
}
}
}
```
**原理**浏览器→Vite 开发服务器同源不跨域→Vite 帮你转发到后端(服务器之间没有跨域限制)。生产环境则由 nginx 做反向代理,原理一样。
---
## 五、Vue 构建层面的适配
微前端框架需要在 Vue 构建工具中做一些特殊配置:
```typescript
// vite.config.ts
vue({
template: {
compilerOptions: {
isCustomElement: tag => /^micro-app/.test(tag)
// ← 告诉 Vue遇到 <micro-app> 标签不要报错,它是自定义元素
}
}
})
```
**通俗理解**Vue 默认不认识 `<micro-app>` 这个标签会报警告。加上这个配置后Vue 就知道"这是个自定义元素,不用管它"。
---
## 六、生命周期管理(防内存泄漏)
每个子应用的代理组件都遵循统一的生命周期:
```typescript
// 挂载时:加载子应用
onMounted(() => {
microApp.renderApp({ name: 'xxx', url: origin, container: '#xxx' });
});
// 卸载时:销毁子应用
onBeforeUnmount(() => {
microApp.unmountApp('xxx'); // 卸载子应用
microApp.clearDataListener(); // 清理数据监听器(避免内存泄漏)
});
```
**为什么重要**:如果不卸载,子应用的 DOM 事件监听器、定时器、网络请求都还在运行,会造成内存泄漏。
---
## 七、整体架构一句话总结
```
主应用(壳)
├── 顶部导航 + 侧边栏 + 路由调度(主应用负责)
├── 态势感知/生态/社区模块(直接写在主应用里)
└── 3 个子应用(通过 micro-app 框架嵌入)
├── 首页/搜索(独立仓库,独立域名)
├── 用户中心(独立仓库,独立域名)
└── AI Copilot独立仓库独立域名
通信方式setData主→子+ addDataListener子→主+ 事件总线(双向)
隔离方式iframe 模式(样式/JS 完全隔离)
路由同步:双向监听,互相通知
```
---
## 八、面试问答准备
### Q1"采用微前端架构 MicroApps 支持模块化开发"具体做了什么?
> 我们平台使用京东开源的 micro-app 框架,把整个应用拆成了主应用 + 3 个子应用。主应用负责导航栏、侧边栏和路由调度3 个子应用首页搜索、用户中心、AI Copilot各自独立开发、独立部署。主应用通过 micro-app 框架将子应用嵌入到自己的页面中,使用 iframe 模式实现样式和 JS 的完全隔离,同时通过 setData、addDataListener 和事件总线三种方式实现主子应用之间的数据通信。
### Q2为什么选择 micro-app 而不是 qiankun
> micro-app 是京东开源的下一代微前端框架,相比 qiankun 有几个优势:第一,接入成本低,只需要一个 `<micro-app>` 标签就能加载子应用,不需要像 qiankun 那样改造子应用的入口文件和打包配置;第二,支持 iframe 模式,能实现更好的样式和 JS 隔离;第三,提供了 setData/addDataListener API 让主子应用通信更方便。
### Q3主应用和子应用之间怎么通信
> 我们用了三种方式:第一,主应用通过 `microApp.setData()` 主动推送数据给子应用,比如路由变化、搜索关键词;第二,主应用通过 `microApp.addDataListener()` 监听子应用发来的消息,比如用户关注状态更新、路由跳转请求;第三,通过共享的 mitt 事件总线实现双向通信,主应用创建事件总线实例后通过 renderApp 的 data 参数传给子应用。路由同步方面,主应用和子应用互相监听路由变化,保持双向同步。
### Q4微前端的隔离和跨域怎么处理的
> 我们用的是 iframe 模式(`iframe: true`),每个子应用运行在独立的 iframe 中。iframe 天然提供了 JS 作用域隔离和 CSS 样式隔离子应用的全局变量、样式都不会污染主应用。跨域方面iframe 模式下主子应用通信靠 `postMessage`,这是浏览器原生 API天然支持跨域不需要额外配置 CORS。开发环境主应用调后端 API 的跨域问题,则通过 Vite 的 proxy 代理解决。
### Q5子应用加载会不会很慢
> 我们做了两个优化:第一,在 index.html 中通过 DNS prefetch 提前解析子应用域名,用户还没点到子应用页面时浏览器就已经在解析域名了;第二,子应用只在用户真正访问对应路由时才加载,不是一进主应用就把所有子应用都加载出来,是按需加载的。
### Q6开发环境和生产环境的子应用地址不一样怎么办
> 通过 Vite 的环境变量机制管理。每个环境(开发/测试/预发/生产)都有独立的 `.env` 文件,里面配置了对应环境的子应用 URL。代码中通过 `import.meta.env.VITE_CHILD_HOMEWEB_HOST` 读取,构建时自动替换为对应环境的地址,不需要改代码。
---
## 九、关键数字(面试时用)
| 数据 | 数字 |
|------|------|
| 微前端框架 | micro-app 1.0.0-rc.3(京东开源) |
| 子应用数量 | 3 个 |
| 通信方式 | 3 种setData / addDataListener / 事件总线) |
| 隔离方式 | iframe 模式(完全隔离) |
| 环境配置 | 4 套(开发/测试/预发/生产) |
| 代理组件 | 3 个(每个子应用对应一个) |
---
## 十、涉及的源码文件(需要看的时候查)
| 做什么 | 文件在哪 |
|--------|----------|
| 框架依赖声明 | `package.json`@micro-zoe/micro-app |
| 构建配置(自定义元素+CORS | `vite.config.ts` |
| DNS 预解析 | `index.html` |
| 环境变量(子应用地址) | `.env.development` / `.env.production` 等 |
| 首页子应用代理 | `src/views/micro-page/proxy-homeweb.vue` |
| 用户中心子应用代理 | `src/views/UserCenter/proxy.vue` |
| AI Copilot 子应用代理 | `src/views/micro-page/proxy-copilot.vue` |
| 路由同步工具 | `src/utils/microAppConfig.ts` |
| 事件总线 | `src/utils/eventBus.ts` |
| 路由配置(带子应用标记) | `src/router/config/home.ts` / `user.ts` / `setting.ts` |
| 布局中的通信监听 | `src/layouts/v2/DefaultLayout/index.vue` |