Files
Situation-Awareness-Platfor…/docs/微前端架构MicroApps模块化开发实现详解.md

404 lines
17 KiB
Markdown
Raw Normal View History

# 微前端架构 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` |