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

17 KiB
Raw Blame 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 文件配置:

# .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 标签一样简单:

<!-- 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 手动调用渲染:

// 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 种通信方式:

方式 1setData — 主应用主动发消息给子应用

// 主应用告诉用户中心"路由变了"
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
});

方式 2addDataListener — 主应用监听子应用的消息

// 主应用监听用户中心发来的各种消息
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 事件库

// 主应用创建事件总线,传给子应用
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 路由怎么同步?

主应用和子应用各有各的路由,需要保持同步:

// 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 预解析

<!-- 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 代理解决:

// vite.config.ts
server: {
  proxy: {
    '/api': {
      target: 'http://222.20.126.217:8100',  // 后端地址
      changeOrigin: true,                      // 修改 Origin 头,绕过同源策略
    }
  }
}

原理浏览器→Vite 开发服务器同源不跨域→Vite 帮你转发到后端(服务器之间没有跨域限制)。生产环境则由 nginx 做反向代理,原理一样。


五、Vue 构建层面的适配

微前端框架需要在 Vue 构建工具中做一些特殊配置:

// vite.config.ts
vue({
  template: {
    compilerOptions: {
      isCustomElement: tag => /^micro-app/.test(tag)
      // ← 告诉 Vue遇到 <micro-app> 标签不要报错,它是自定义元素
    }
  }
})

通俗理解Vue 默认不认识 <micro-app> 这个标签会报警告。加上这个配置后Vue 就知道"这是个自定义元素,不用管它"。


六、生命周期管理(防内存泄漏)

每个子应用的代理组件都遵循统一的生命周期:

// 挂载时:加载子应用
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