想象一下,你们公司有一栋“巨型写字楼”,所有业务团队都挤在同一层楼里办公。几百人在开放式工位里敲代码、改系统,一个团队上线新功能,整层楼都要停机维护;一个团队改了全局样式,其他团队的页面瞬间崩掉——这就是单体前端应用的日常噩梦。为了让大团队像小团队一样独立作战,前端界催生了「微前端」理念,而落地它最主流的两种方案,就是 qiankun 和 Module Federation。 qiankun 像一位“物业大管家”:它把写字楼划成独立商铺,统一搞定水电、门禁和装修隔离,子应用随时入驻、随时搬走。Module Federation 则更像“中央共享仓库”:每个团队各自建楼独立运营,但所有楼群共享一个公共资源库,谁缺组件、缺依赖,都能从仓库里实时取用。两种路线、两套哲学,谁更适合你的团队,正是这篇文章要解决的问题。 学完这篇,你将能分清 qiankun 和 Module Federation 的技术路线差异,在面试和架构评审中自信地给出微前端选型建议——这是中高级前端工程师和前端架构师的核心能力。

岗位关联

需要学习微前端(qiankun/Module Federation),扎实的编程功底是AI应用开发的基础。本篇涵盖Python、Git、Docker、IDE与开发工具链,帮助你掌握工程化开发能力。

目录
  1. 可视化演示
  2. 微前端概述
  3. qiankun核心原理
  4. JS沙箱机制
  5. 样式隔离方案
  6. 模块联邦原理
  7. 通信机制设计
  8. 部署差异对比
  9. 选型实践指南
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示两种主流微前端方案:qiankun基于路由劫持和JS沙箱的子应用隔离、Module Federation基于Webpack5的模块共享与运行时注入、以及五维度横向对比。交互切换架构视图。

本节配有交互动效(进入视口后自动加载,约 3KB)。

qiankun微前端架构 主应用→路由劫持→JS沙箱→子应用加载 沙箱隔离子应用全局变量防止污染 loadApp(entry, { sandbox: true }) qiankun通过HTML entry+JS沙箱实现子应用隔离

微前端概述

想象一家搬到新办公楼的科技公司。最初大家挤在同一个大平层,工位之间没有任何隔断,市场部改个海报,全楼都要一起换装修;研发部升级服务器,整层楼的 WiFi 都要断掉。公司决定按业务线重新划分空间,让每个团队拥有自己的独立办公区——这个“拆分再组合”的思路,就是微前端。

微前端不是某个框架,而是一种架构思想:把大型前端应用按业务边界拆成多个可独立开发、独立部署的子应用,再通过某种机制把它们组合成一个对用户来说完整统一的产品。它与后端微服务的理念同源,只不过切分的是浏览器里那些会互相踩脚趾的 JS 和 CSS。拆完之后,两个团队再也不用因为同一个仓库的合并冲突而互相等待。

业界落地微前端有两条主流路线。qiankun 走的是“物业式管控”:一个主应用当“壳”,负责把各个子应用装进自己的页面容器里,并提供沙箱、样式隔离等基础能力。Module Federation 走的是“仓库式共享”:它基于 Webpack5,让多个应用不仅各自独立运行,还能在运行时互相加载对方的模块,实现真正意义上的资源共享。

回到大楼的比喻:无论是请物业统一管理,还是建公共仓库共享资源,最终目的都是让这栋楼从“一锅粥”变成“井然有序的园区”。微前端要解决的就是组织协作问题,而不是单纯的技术炫技。

F(x)=∑i=1nfi(x)\displaystyle F(x) = \sum_{i=1}^n f_i(x)
整体应用等于多个子应用之和,每个子应用都能独立演进,最后组合成完整产品。
  1. 拆分重组把大型前端拆成多个独立的业务单元。每一个 $f_i$ 单独开发、单独上线,谁也不挡谁的路,就像把大平层改造成一间间独立办公室。
    F(x)=∑i=1nfi(x)F(x) = \sum_{i=1}^n f_i(x)
  2. 独立部署每个子应用拥有自己的代码仓库和发布流程,一个团队发版不需要拉上全楼的人一起熬夜。
    fi∈Appsf_i \in \text{Apps}
  3. 组合机制用 qiankun 或 Module Federation 把散装的子应用重新拼成一个完整产品,用户无感知。
    ⊕:(f1,f2,…,fn)→F\oplus : (f_1, f_2, \dots, f_n) \rightarrow F

qiankun核心原理

访客走进一栋由物业管理的写字楼,第一步不是直接冲向楼层,而是先到前台。前台根据访客要找的人(路径),查看登记表(激活规则),然后告诉访客该去几楼几号房。qiankun 的主应用就是那个“前台”,子应用就是楼里的各个公司。

qiankun 基于 single-spa 这类微前端基座库发展而来。它通过监听浏览器地址栏的变化,在路由切换时动态决定应该由哪个子应用来接管页面的渲染。当用户访问 /order/list 时,主应用发现这个路径属于“订单应用”,于是把订单应用加载到指定的 DOM 容器中,挂载展示;当用户离开这个路径,再把它卸载清理。这一切都在运行时发生,不需要重新构建主应用。

qiankun 有两个核心设计。第一是路由劫持:它重写了 window.history 和 hashchange 事件,让路由变化时能通知所有已注册的子应用去判断自己是否该激活。第二是 HTML entry:与传统的 JS entry 不同,qiankun 允许子应用直接暴露一个 HTML 文件地址,主应用像浏览器一样去 fetch 这个 HTML,然后解析出里面的 JS 和 CSS 再执行——这意味着子应用可以用任何技术栈开发,Vue、React、Angular、甚至原生 JS 都能接入。

物业前台的比喻在这里非常贴切:qiankun 的重担就是“正确地引导访客”,而这依赖一张清晰的登记表(路由规则)。一旦登记表混乱,访客就会走错楼层——这也是微前端架构中最常见的翻车现场。

hit=startsWith(path,  rule)\displaystyle \text{hit} = \text{startsWith}\left(\text{path}, \; \text{rule}\right)
路由匹配的本质:用当前浏览器路径去比对子应用注册的激活规则,前缀命中即激活。
  1. 路径状态浏览器地址栏中的 pathname 是路由分发的唯一依据,比如 /order/list。
    path=window.location.pathname\text{path} = \text{window.location.pathname}
  2. 激活规则每个子应用注册时声明自己负责的路由前缀,像公司在前台登记的楼层信息。
    rulei=prefixi\text{rule}_i = \text{prefix}_i
  3. 匹配判定主应用调用字符串前缀匹配,判断当前路径是否属于某个子应用。
    hit=startsWith(path,rulei)\text{hit} = \text{startsWith}(\text{path}, \text{rule}_i)

JS沙箱机制

物业为了防止租户之间互相干扰,给每个商铺装上了独立电表和门禁系统。租户在自己屋里开多少空调、装多少电器都行,但电费只走自己的表;租户退租时,物业把屋子恢复到初始状态,下一位租户进来时看到的还是那间干净的空房。qiankun 的 JS 沙箱做的就是这件事。

子应用运行时会往全局对象 window 上挂载各种变量、设置定时器、绑定事件监听器。如果不做隔离,子应用 A 卸载后,它在 window 上留下的全局变量会继续存在,可能覆盖子应用 B 的同名变量,导致数据错乱甚至白屏。qiankun 提供了快照沙箱和代理沙箱两种主流方案来解决这个问题。

快照沙箱的原理很朴素:子应用激活前,把 window 上所有属性复制一份快照;子应用运行期间随意修改;卸载时,遍历快照把所有属性恢复原状。代理沙箱则更优雅:它利用 ES6 的 Proxy 给 window 套了一层拦截器,子应用读取某个属性时先查虚拟环境,查不到再找真实 window;写属性时只写入虚拟对象,真实 window 完全不被触碰。代理沙箱性能更好,也是 qiankun 默认推荐的方式。

电表不会因为楼上租户多开了一台空调而跟着跳闸,代理沙箱也不会因为子应用 A 写了几个全局变量而影响子应用 B 的运行。沙箱是微前端信心的基石,但记住它只负责 JS 的隔离,CSS 的隔离还得靠下一节的招数。

W′=Proxy(W,  get,  set)\displaystyle \mathcal{W}' = \text{Proxy}\left(\mathcal{W}, \; \text{get}, \; \text{set}\right)
代理沙箱给全局对象加了一层“翻译官”,子应用的读写都经过翻译官,操作落不到真正的 window 上。
  1. 代理封装用 Proxy 把真实的 window 对象包裹起来,返回一个新对象给子应用使用。
    W′=Proxy(W)\mathcal{W}' = \text{Proxy}(\mathcal{W})
  2. 读取拦截子应用读属性时,代理先查虚拟环境,找不到才回退到真实 window。
    get(target,key)\text{get}(target, key)
  3. 写入拦截子应用写属性时,代理把值存在虚拟对象里,真实 window 完全不被污染。
    set(target,key,value)\text{set}(target, key, value)

样式隔离方案

商场物业对每间店铺的装修有一条硬性规定:店铺招牌必须带商场统一的编号前缀,比如 A-301、B-205。这样即使两家店都卖奶茶,顾客也不会走错门。CSS 样式隔离的思路也是如此——给每个子应用的样式起一个“带编号的名字”。

子应用的 CSS 默认是全局的,如果子应用 A 写了 .btn { background: red },子应用 B 也写了 .btn { background: blue },后加载的样式会覆盖先加载的,页面颜色就乱了。qiankun 提供了三种层次的样式隔离手段。最彻底的是 Shadow DOM 严格隔离:把子应用的 DOM 放进一个 shadowRoot 里,浏览器天然保证内外样式互不可见,但这种方案对弹窗、浮层等挂载到 body 下的元素无能为力。

第二层是实验性样式隔离:qiankun 在运行时扫描子应用加载的样式表,给每个选择器加上一个带应用名前缀的属性选择器,比如 .btn 变成 .order-app .btn,这样即使两个应用都有 .btn,作用域也不同。第三层则是团队约定:大家约好每个类名都带命名空间,比如 .order-btn、.user-avatar,用代码规范代替运行时开销。这三种方案可以组合使用,实际项目中最常见的是“实验性隔离 + 命名约定”双保险。

装修再美观,招牌编号乱了也会让人走错门。样式隔离不一定需要做到 100% 物理隔离,能让两个团队在各自的“命名空间”里安心写样式,就已经解决了 90% 的冲突问题。

style′=prefix(selector,  appName)\displaystyle \text{style}' = \text{prefix}\left(\text{selector}, \; \text{appName}\right)
样式隔离的核心是给选择器加上应用名前缀,让同名类在不同子应用内各用各的。
  1. 原始样式子应用开发者写的标准 CSS 规则,比如 .btn { color: red }。
    css={selector:declarations}\text{css} = \{ \text{selector} : \text{declarations} \}
  2. 命名空间映射给所有选择器加上应用名作为前缀,避免全局冲突。
    selector′=appName-selector\text{selector}' = \text{appName} \text{-} \text{selector}
  3. 作用域落地改写后的样式只在子应用自己的容器内生效,不会泄漏到别的应用。
    style′=prefix(css,appName)\text{style}' = \text{prefix}(\text{css}, \text{appName})

模块联邦原理

如果说 qiankun 是请物业来管楼,Module Federation 就是干脆让每个团队自己建楼,然后在园区中心修一座“中央共享仓库”。市场部需要一套模板组件,不用自己造轮子,直接从仓库里拿;研发部升级了共享工具库,所有楼群第二天自动用上新版本。

Module Federation 是 Webpack5 内置的模块共享能力。它在构建阶段通过 ModuleFederationPlugin 插件声明两件小事:本应用要“暴露”哪些模块给别人(exposes),以及本应用要“借用”哪些共享依赖(shared)。构建完成后,每个应用还是一个独立的可部署产物,但产物里多了一份“远程模块的引用地图”,真正加载远端模块发生在浏览器运行时。

模块联邦解决的是“运行时依赖注入”问题:应用 A 的代码里可以 import('appB/Button'),构建时 Webpack 不打包这份代码,而是生成一个运行时加载函数;当浏览器执行到这行代码时,才去应用 B 的服务器上请求 Button 模块。同时,shared 机制还能让多个应用共享同一个 React 实例,避免重复加载导致的状态不同步。这种“用的时候才去拿”的方式,比把所有公共代码打进每个应用的包里高效得多。

中央仓库的意义在于“人人可共享、处处不重复”。但仓库也需要管理规则:谁负责维护共享包的质量?版本升级如何通知所有群租楼?这些问题在部署与版本管理部分会进一步展开。

load(remotej)→Module\displaystyle \text{load}\left(\text{remote}_j\right) \rightarrow \text{Module}
模块联邦的运行时加载:构建期只留引用,浏览器运行到那一刻才从远端拉取真正的模块。
  1. 暴露模块应用通过 exposes 字段描述哪些模块可以被其他应用远程加载。
    exposedj={keyj:modulej}\text{exposed}_j = \{ \text{key}_j : \text{module}_j \}
  2. 共享依赖通过 shared 字段声明哪些公共依赖希望全局只有一份,如 react、vue。
    shared={react,vue,… }\text{shared} = \{ \text{react}, \text{vue}, \dots \}
  3. 运行时解析构建产物中只有 remote 的引用地址,执行时才通过 jsonp 加载远端模块。
    load(remotej)→Module\text{load}(\text{remote}_j) \rightarrow \text{Module}

通信机制设计

大楼里的商户之间免不了要传递消息:奶茶店要向会员中心确认顾客的积分,商场广播要通知所有店铺新的促销活动。微前端里的子应用通信,就是设计一套清晰的“传话系统”,让消息不靠吼,而是走正规渠道。

qiankun 和 Module Federation 都支持多种通信方式,团队可以根据耦合程度选择。第一种是 props 透传:主应用在挂载子应用时,把需要的数据和方法作为 props 传进去。这像物业在店铺开业时递上一本《商户手册》——一次性给到位。第二种是全局状态管理:在主应用中维护一个全局状态容器(比如 Redux 或 Zustand),子应用通过订阅状态变化来响应更新,适合跨应用共享登录态、用户信息等高频数据。第三种是自定义事件:通过浏览器的 window.dispatchEvent 和 addEventListener 实现事件广播,最适合松耦合的“通知型”通信,比如“订单已创建,请刷新列表”。

通信设计最怕的是“没有约定”。如果所有子应用都直接 import 一个全局单例对象,改动时牵一发动全身,耦合度比单体应用还高。所以微前端的最佳实践是把通信协议文档化:哪些数据用 props 注入、哪些状态进全局 store、哪些消息走事件总线,都事先约定清楚。

回到商场比喻:传话系统越规范,商户之间的协作越顺畅。无论是物业手册(props)还是商场广播(事件),关键是让每个商户知道“该听谁的广播、该按哪本手册办事”,而不是遇到问题就伸长脖子喊一嗓子。

statet+1=reducer(statet,  action)\displaystyle \text{state}_{t+1} = \text{reducer}\left(\text{state}_t, \; \text{action}\right)
全局状态通信的本质:任何一次消息传递都对应一次状态迁移,所有应用订阅同一份真相。
  1. 动作产生用户操作或业务事件产生一个标准消息对象,比如 { type: 'LOGIN', payload: user }。
    action={type,payload}\text{action} = \{ \text{type}, \text{payload} \}
  2. 状态流转reducer 根据旧状态和 action 计算新状态,这是 Redux 等全局状态库的通用模型。
    statet+1=reducer(statet,action)\text{state}_{t+1} = \text{reducer}(\text{state}_t, \text{action})
  3. 订阅通知其他子应用订阅状态变化后自动刷新界面,完成跨应用联动。
    update=subscribe(statet+1)\text{update} = \text{subscribe}(\text{state}_{t+1})

部署差异对比

请物业管理的商场和各自建楼的产业园,在“盖房子”这件事上的流程完全不同。商场是统一规划、分店铺装修;产业园是各家独立施工、再修路连通。qiankun 和 Module Federation 的部署差异,恰好也具有这种结构性的不同。

qiankun 的部署模式是“一个壳 + 多个独立子应用”。主应用和各子应用分别构建、分别部署到不同的静态服务器或 CDN 路径上。主应用通过配置的子应用 HTML 地址去加载它们,所以子应用发版完全不需要主应用重新构建——这非常适合多团队并行发布的场景。版本管理也简单:每个子应用的 URL 就是它的版本号,切换版本只需更新主应用的注册配置。

Module Federation 的部署模式则更接近“统一构建生态”。虽然每个应用也是独立部署,但由于存在运行时互相加载的依赖关系,远端模块一旦发布新版本,所有引用它的宿主应用在下次访问时会自动拿到新代码。好处是模块复用极高效,风险是版本失控:一个远端应用发布了一个破坏性变更,可能让所有宿主应用同时报错。因此 MF 的版本管理必须更精细,常见的策略是 shared 依赖制定版本范围,并结合构建时的 requiredVersion 校验。

商场有商场的规范,产业园有产业园的自由。qiankun 的部署模式对运维友好、故障隔离直观;MF 的部署模式对共享高效、但需要更强的工程治理能力。选择哪种,取决于你们团队能不能驾驭“自由的代价”。

compat=1[vmin⁡,vmax⁡](vhost)\displaystyle \text{compat} = \mathbf{1}_{[v_{\min}, v_{\max}]}\left(v_{\text{host}}\right)
模块联邦的版本兼容判断:宿主提供的共享依赖版本必须落在声明范围内,否则加载失败或降级为独立副本。
  1. 版本约束每个共享依赖声明它所需的版本区间,像租户在仓库借货时写明“需要 18.x 以上的规格”。
    v∈[vmin⁡,vmax⁡]v \in [v_{\min}, v_{\max}]
  2. 宿主版本运行时检查宿主提供的实际版本号是否落在这个区间内。
    vhost∈[vmin⁡,vmax⁡]v_{\text{host}} \in [v_{\min}, v_{\max}]
  3. 兼容结论满足区间则共享,否则返回 0 并自动 fallback 到加载自己的副本。
    compat=1[vmin⁡,vmax⁡](vhost)\text{compat} = \mathbf{1}_{[v_{\min}, v_{\max}]}(v_{\text{host}})

选型实践指南

面对物业和中央仓库两种方案,决策者不该拍脑袋,而要像技术委员会一样系统地评估三个维度:团队规模、技术栈匹配、共享需求强度。

如果团队规模大、技术栈杂、业务系统多,需要清晰的“壳 + 子应用”边界来治理,qiankun 是更稳妥的选择。它接入成本低,对已有系统的侵入性小,子应用可以用任何框架改造,甚至老旧的 jQuery 项目也能慢慢迁移进来。如果团队规模小、技术栈统一、多个应用之间有大量需要复用的业务组件和公共依赖,Module Federation 会更顺手——它把共享写进了构建工具里,不用像 qiankun 那样在运行时做层层包裹。

技术栈匹配也很关键:项目还在用 Webpack4,迁移到 MF 需要先升级 Webpack5,工作量大;而 qiankun 对构建工具没要求。反过来,如果团队本来就在 Webpack5 上,而且痛点不是“应用隔离”而是“模块复用”,那 MF 的优势就非常明显。最后是治理能力:MF 的运行时共享虽然灵活,但版本失控和循环依赖风险都需要强大的工程规范来兜底;qiankun 的沙箱和样式隔离则更像“强制纪律”,天然约束了子应用的越界行为。

选物业还是建仓库,没有标准答案,只有“最适合你们园区现状”的答案。更聪明的做法是认识到两者并不对立:qiankun 可以装入用 MF 构建的子应用,两种方案可以组合使用,让物业管隔离、仓库管共享。

Choice=F(Team,  Arch,  Governance)\displaystyle \text{Choice} = \mathcal{F}\left(\text{Team}, \; \text{Arch}, \; \text{Governance}\right)
选型是三维度的综合决策函数:团队、架构、治理能力共同决定最终选择。
  1. 团队评估考察团队人数、技术栈分布和协作模式,人多且杂时更倾向 qiankun 的强隔离。
    Team={size,skill}\text{Team} = \{ \text{size}, \text{skill} \}
  2. 架构评估评估现有构建链路和部署环境,Webpack5 生态下 MF 的接入成本更低。
    Arch={build,deploy}\text{Arch} = \{ \text{build}, \text{deploy} \}
  3. 治理评估评估代码仓库归属和发布节奏,高频发版且模块共享多的团队更适合 MF。
    Governance={repo,rhythm}\text{Governance} = \{ \text{repo}, \text{rhythm} \}
  4. 综合决策把三个维度代入决策函数,得到一个适合自身场景的答案。
    Choice=F(Team,Arch,Governance)\text{Choice} = \mathcal{F}(\text{Team}, \text{Arch}, \text{Governance})

AI 应用场景

AI 中台的多团队前端协作

阶段:模型服务的可视化交付

解决的问题:AI 团队内部通常有算法组、平台组、标注组并行开发,各组的技术栈和发布节奏不同。传统的单体前端需要所有组协商一个发布窗口,改动频繁的算法可视化界面往往被平台组的版本节奏拖住。

具体案例:某 AI 公司的模型训练平台采用 qiankun 重构,把“数据集管理”“模型训练监控”“评估报告”拆成三个子应用。算法组用 React 维护训练监控,平台组用 Vue 维护数据管理,标注组甚至用原生 JS 做了一个快速标注工具。主应用统一提供导航和权限,子应用各自发版,原本两周一次的联调变成了各团队随时独立上线。

AI 可视化平台的模块联邦共享

阶段:模型的在线推理与结果展示

解决的问题:同一个 AI 团队维护多个面向不同业务线的可视化产品,每个产品都需要“模型结构图”“推理耗时曲线”“样本分布散点图”等通用组件。如果每个应用各写一份,组件 bug 要改多处;如果抽成 npm 包,每次发版都要全量升级。

具体案例:团队用 Module Federation 搭建了一个“可视化组件仓库”,公共组件仓库在共享中心统一维护,多个 AI 应用通过 exposes 和 shared 在运行时动态加载相同的组件实例。当工程师修复了散点图中的一处渲染 bug,所有应用在下次访问时自动拿到修复版本,不需要任何额外部署。

Python 演示

python73 行
"""模拟微前端两种方案:qiankun 的沙箱隔离与 Module Federation 的共享模块。"""  class App:    """一个微前端子应用,模拟注册时的基本信息。"""     def __init__(self, name, active_rule):        self.name = name        self.active_rule = active_rule     def match(self, path):        """判断当前路由是否命中本应用。"""        return path.startswith(self.active_rule)  class QiankunSandbox:    """模拟 qiankun 快照沙箱:激活时记录,卸载时还原。"""     def __init__(self, window_env):        self.window_env = window_env        self.snapshot = {}        self.modified_keys = set()     def grant(self):        self.snapshot = dict(self.window_env)     def set_attr(self, key, value):        self.window_env[key] = value        self.modified_keys.add(key)     def revoke(self):        for key in self.modified_keys:            if key in self.snapshot:                self.window_env[key] = self.snapshot[key]            else:                del self.window_env[key]        self.modified_keys.clear()  class ModuleFederationRegistry:    """模拟 Module Federation 的共享模块池。"""     def __init__(self):        self.module_pool = {}     def get_or_create(self, module_name, version, factory):        pool_key = (module_name, version)        if pool_key not in self.module_pool:            self.module_pool[pool_key] = factory()        return self.module_pool[pool_key]  def main():    # qiankun 场景:子应用运行期间修改全局变量,卸载后还原    window_env = {"currentTheme": "light"}    sandbox = QiankunSandbox(window_env)     sandbox.grant()    sandbox.set_attr("currentTheme", "dark")    print("子应用运行期间:", window_env)     sandbox.revoke()    print("子应用卸载后还原:", window_env)     # Module Federation 场景:多个应用共享同一个模块实例    registry = ModuleFederationRegistry()    react_a = registry.get_or_create("react", "18.2.0", lambda: {"name": "react"})    react_b = registry.get_or_create("react", "18.2.0", lambda: {"name": "duplicate"})    print("两个应用拿到同一份 React?:", react_a is react_b)  if __name__ == "__main__":    main()
目的:用 Python 模拟微前端两大核心机制——qiankun 的快照沙箱如何隔离并还原全局变量,以及 Module Federation 的共享池如何让多个应用复用同一个模块实例。
  1. def match(self, path):每个子应用声明自己的路由激活规则,类似物业前台查登记表决定访客去向。
  2. self.snapshot = dict(self.window_env)子应用启动前给 window 拍快照,记录修改前的原始状态。
  3. self.window_env[key] = value子应用运行期间可以修改全局对象,模拟真实业务的全局变量写入。
  4. if key in self.snapshot: self.window_env[key] = self.snapshot[key]卸载时用快照还原,清理子应用的全局污染。
  5. pool_key = (module_name, version)MF 按“名字 + 版本”作为共享模块的键,避免不同版本的模块互相覆盖。
  6. if pool_key not in self.module_pool:命中共享则直接复用,不命中才执行 factory 创建新实例,这就是单例共享的原理。

常见误区

误区很多人第一次听说微前端就想到 iframe,以为 qiankun 只是把子应用塞进 iframe 里的高级封装。反正子应用都是独立页面,用 iframe 不也能隔离吗?

正解qiankun 与 iframe 有本质区别。iframe 是浏览器级别的页面隔离,虽然隔离彻底,但带来了致命问题:路由不同步、弹窗无法跨域、应用间通信极其别扭、性能开销大、而且无法共享浏览器原生能力。qiankun 则是基于 single-spa 的框架级方案,子应用和主应用在同一个文档流中渲染,路由共享地址栏,通过沙箱和样式隔离来模拟“隔离”,兼顾了隔离性和体验。

为何常见:因为 iframe 是前端世界里最直观的“页面套页面”方案,很多人会自然地把它和微前端划等号。

误区以为用了 MF 的 shared 配置后,所有应用加载的 React 或 Vue 都是同一个单例,绝不会出现状态不同步的问题。

正解MF 的共享语义是“尽力而为”。只有当两个应用声明的 shared 版本区间完全匹配时,Webpack 才会复用同一个实例;如果版本范围不兼容,Webpack 会静默地各自加载自己的副本。要让共享真正生效,必须显式配置 singleton: true 并保证各应用的依赖版本在指定范围内,否则就只是“看起来共享了”。

为何常见:因为官方文档强调“共享依赖”,初学者容易把理想情况当成默认行为。

误区以为有了 qiankun 的代理沙箱,子应用无论怎么写全局变量、开多少定时器、绑多少事件监听,都绝对不会影响其他子应用。

正解沙箱只能隔离对 window 对象的属性读写,但拦不住所有副作用。比如子应用修改了 document.title、操作了 localStorage、或者往 body 上挂 DOM 节点,这些属于沙箱管不到的“外部状态”。qiankun 提供了 getMicroAppStateActions 等辅助,但真正的副作用清理还要靠子应用在 unmount 生命周期里手动清理定时器和事件。

为何常见:开发者容易把“沙箱”理解成浏览器级的安全边界,而它实际只是一层 JS 层面的拦截代理。

误区很多团队迁移到微前端是冲着“各团队独立发版”去的,以为只要拆了应用就能彻底摆脱发布节奏的束缚。

正解并行发布是有条件的。qiankun 模式下子应用确实可以独立部署,但主应用的路由配置变化仍需要主应用发版;Module Federation 模式下,远端模块一旦破坏性升级,所有引用它的宿主应用都会受影响。真正的并行发布要求团队同时具备版本兼容意识、接口契约测试和灰度发布能力,微前端只是提供了可能,而不是自动兑现。

为何常见:因为微前端的宣传语往往强调“独立部署”,让人忽略了它是团队协作模式的革命,而不只是技术架构的革命。

误区把两种方案看成非此即彼的竞争对手,做选型时必须在两颗“银弹”里挑一颗。

正解两者解决的问题维度不同。qiankun 解决的是“应用隔离与统一壳管理”,Module Federation 解决的是“模块共享与运行时依赖注入”。在实际项目中完全可以组合使用:用 qiankun 搭建微前端壳,整体管理各个子应用的挂载与隔离;在内部再用 MF 让几个关联密切的子应用共享通用组件和依赖。许多大型中后台系统正是这样混合落地的。

为何常见:因为技术文章倾向于把框架对比着写,容易让读者形成“有你无我”的认知惯性。

面试题

Q1. qiankun 的 JS 沙箱核心作用是以下哪项?

单选题 · easy

  • A.为子应用加速加载性能
  • B.隔离全局 window 对象污染← 正确答案
  • C.将子应用打包为独立文件
  • D.让子应用样式自动加前缀

答案:B。JS 沙箱的核心目的是隔离子应用对 window 全局对象的读写,避免变量互相覆盖。加速加载、打包、样式前缀都不是沙箱的职责,所以 A、C、D 都是干扰项。沙箱就像电表,只管隔离环境,不管省电。

Q2. qiankun 是基于以下哪个库实现的?

单选题 · easy

  • A.Webpack
  • B.single-spa← 正确答案
  • C.Redux
  • D.Express

答案:B。qiankun 基于 single-spa 这个微前端基座库扩展而来,继承了它的应用注册和生命周期管理能力。Webpack 是构建工具,Redux 是状态管理,Express 是后端框架,都与 qiankun 的底基无关。

Q3. qiankun 的 HTML entry 加载方式最突出的优点是?

单选题 · mid

  • A.子应用可用任意技术栈开发← 正确答案
  • B.子应用必须使用 Webpack 构建
  • C.子应用不需要提供任何入口
  • D.子应用和主应用必须同框架

答案:A。HTML entry 意味着主应用直接 fetch 子应用的 HTML 文件,再解析出其中引用的 JS 和 CSS。这种方式完全不关心子应用用什么框架、什么构建工具,因此技术栈完全解耦。B、C、D 都是对 HTML entry 的错误理解。

Q4. 以下关于 Module Federation 的说法正确的有?

多选题 · mid

  • A.它基于 Webpack5 实现← 正确答案
  • B.只有主应用能共享模块
  • C.应用可同时是宿主和远端← 正确答案
  • D.它不需要修改构建配置

答案:AC。MF 是 Webpack5 的原生能力,需要通过 ModuleFederationPlugin 配置 exposes 和 shared。任何应用都可以既暴露模块给他人用(作为 remote),又加载别人的模块(作为 host),所以 C 正确。B 和 D 与 MF 的设计相悖。

Q5. 快照沙箱在子应用退出时如何处理 window?

单选题 · mid

  • A.保留子应用的全部修改
  • B.用快照将 window 还原← 正确答案
  • C.删除 window 的所有属性
  • D.重新创建一个全新的 window

答案:B。快照沙箱的思路是“记录-修改-还原”:启动时拍快照,运行时随意改,退出时用快照覆盖所有修改。它不是删除全部属性(会误伤主应用),也不是重建 window(不可能做到),所以只能选 B。

Q6. qiankun 支持的样式隔离方案包括哪些?

多选题 · mid

  • A.Shadow DOM 严格隔离← 正确答案
  • B.实验性样式隔离← 正确答案
  • C.CSS 命名空间约定← 正确答案
  • D.自动删除全部全局样式

答案:ABC。qiankun 提供了 Shadow DOM 严格隔离、运行时加前缀的实验性隔离,同时社区也普遍采用 CSS 命名空间约定作为低成本方案。D 不是一个合理的方案,删除全局样式会导致页面直接失去基础样式。

Q7. 在 Module Federation 的共享版本冲突中,下列哪种做法最合理?

单选题 · extreme

  • A.强制所有应用锁定相同版本
  • B.通过 shared 策略设置版本区间校验← 正确答案
  • C.完全禁止共享任何第三方依赖
  • D.只允许单向依赖注入共享

答案:B。MF 的 shared 配置可以指定版本的 requiredVersion 区间,运行时自动校验宿主版本是否满足要求,不满足则 fallback 到各自副本。强制锁版本和禁止共享都牺牲了灵活性,单向依赖则违背了 MF 的设计初衷,所以 B 最合理。

Q8. 关于 props 透传,下列描述最准确的是?

单选题 · mid

  • A.父应用在挂载子应用时传递数据与方法← 正确答案
  • B.子应用主动向父应用拉取所有数据
  • C.父应用通过 URL 参数序列化传递数据
  • D.子应用之间直接相互访问内存对象

答案:A。props 透传是主应用在子应用 mount 时,把初始化数据和方法作为参数传进去,像物业给新商户递服务手册。它不是子应用主动拉取(那是订阅模型),不是 URL 参数(长度受限且无法传函数),更不是内存直访(违反隔离原则)。

Q9. 微前端最核心的目标是什么?

单选题 · easy

  • A.让所有页面加载速度更快
  • B.把大型应用拆成可独立开发部署的单元← 正确答案
  • C.彻底消除模块之间的所有依赖
  • D.替代后端的微服务架构

答案:B。微前端解决的是大型前端应用的组织协作问题,核心目标是让团队能独立开发、独立部署自己的业务单元。速度提升是可能的副产品,但不是目标;消除所有依赖在现实中不可能;微前端和微服务是两个层面的东西。

Q10. 微前端选型时需要综合考虑哪些因素?

多选题 · hard

  • A.团队规模与协作模式← 正确答案
  • B.现有技术栈匹配程度← 正确答案
  • C.模块共享需求强度← 正确答案
  • D.单个页面按钮点击数量

答案:ABC。选型应围绕团队规模(人多则 qiankun 强治理)、技术栈(Webpack5 生态适合 MF)、共享需求(模块复用强烈则 MF 优势大)三个维度展开。页面按钮点击数量属于产品交互细节,与架构选型无直接关系。