简介qiankun-example是一份可直接运行的微前端实战Demo主应用基于Vue子应用同时包含React和Vue适合需要解决大型项目拆分、多技术栈协作的开发者。包内共76个文件以js、json、vue等类型为主压缩包仅635KB目录结构清晰包含main主应用、sub-vue、sub-react子应用及common公共模块等可快速定位代码。目前已有1408人下载学习。通过该项目可深入理解qiankun的核心机制主应用如何注册与挂载子应用、子应用独立路由与生命周期管理、跨应用通信方案以及主从应用的环境配置与打包要点。配套的config、env、router等配置项均给出可直接参考的写法适合在Vue主工程中集成React或Vue子应用的场景也为前端团队落地微前端架构提供了一份简洁的模板。1. 微前端不是框架之争这个 qiankun 实战 demo 到底在解决什么一个老系统用 Vue 写了三四年新团队想用 React 重构核心模块两边都要独立发布、独立回滚这不该靠「把代码搬到一个仓库里统一改造成一套框架」来解决微前端才是更现实的出路。qiankun 是眼下踩坑最少、落地最快的运行时方案之一它不要求子应用改造成统一技术栈只要求每个子应用暴露几个生命周期函数。这个 qiankun 实战 demo 用 Vue 搭父应用基座挂一个 Vue 子应用和一个 React 子应用把微前端里最典型的异构场景完整演示了一遍。适合准备做微前端改造但不知道从哪下手的前端开发也适合想确认 qiankun 边界在哪里的架构评审。看完你会有一个能跑的基座也知道最坑的几步藏在哪。2. qiankun 的运行机制JS 沙箱、HTML Entry 与生命周期契约2.1 三种主流微前端路线iframe、自研加载器和 qiankun 的取舍微前端的实现路线其实就三类。第一类是 iframe浏览器天然隔离但通信要过 postMessage状态刷新即丢弹窗、全屏这类体验问题很难绕开第二类是自研加载器模块加载、沙箱、路由联动全自己实现听上去可控实际工程量很大没有专门人力很难长期维护第三类就是 qiankun 这类运行时方案把「加载子应用、创建沙箱、管理生命周期」封装成框架能力业务代码只需要遵守一套契约。实现路线隔离强度通信成本存量改造量落地周期iframe强高低短自研加载器可定制高高长qiankun中低低短这里的「中」需要解释qiankun 的 JS 沙箱能隔离大部分全局变量污染但样式隔离默认不开启要显式配置。对绝大多数中后台系统这个强度完全够用。选型理由很实际如果你的团队里同时存在 Vue 2、Vue 3、React 项目大家不想统一技术栈也不想为拆包重构付出几个月成本qiankun 是当前比较务实的选项。它不是万能的重度依赖浏览器插件、需要强隔离的在线编辑器类应用就要再权衡。2.2 HTML Entry 把子应用「拉」进基座的完整加载链路qiankun 用的是 HTML Entry 而不是 JS Entry。JS Entry 的思路是子应用提供一个入口 JS基座加载它再执行HTML Entry 则直接请求子应用的 index.html解析里面的 script 和 link动态执行并挂载。好处是子应用的构建产物不需要为微前端定制Vue CLI、CRA 默认产物就能直接接入这是它比早期方案对存量项目友好得多的地方。加载路径按顺序是路由匹配 activeRule → 请求子应用入口 HTML → 解析 HTML 抽取脚本和样式 → 在沙箱里执行脚本 → 触发 mount 生命周期 → 内容挂到基座容器。写代码时不用手动处理这六步但心里要有这条链路排查问题时才知道卡在哪一环。这里有两点容易踩。其一请求子应用 HTML 是一个跨域请求子应用开发服务器必须返回Access-Control-Allow-Origin响应头否则请求在浏览器层就被拦掉表现是子应用区域空白控制台报Failed to fetch其二script 被提取后是以字符串方式执行子应用代码里如果依赖document.currentScript这类运行时上下文取值会和预期完全不同。安全策略比较严的站点还要提前确认 CSP 是否允许动态脚本否则接入后期会大面积返工。2.3 生命周期契约子应用必须暴露 bootstrap / mount / unmountqiankun 不关心子应用用什么框架只关心入口文件有没有导出三个异步函数。这三个函数的触发时机和职责在接入前要背下来生命周期触发时机职责bootstrap首次进入前全局只执行一次放一次性初始化逻辑mount每次进入子应用创建实例、渲染页面、绑定事件unmount每次切走或卸载销毁实例、移除事件、清理定时器最小入口写法如下export async function bootstrap() { // 只执行一次适合放埋点初始化 } export async function mount(props) { // props.container基座传入的挂载容器 // props 里还可能有基座下发的业务参数 } export async function unmount() { // 销毁实例移除 DOM 和事件 }这三个函数要在构建产物里能被框架识别。qiankun 加载 UMD 包后从全局对象上读取导出所以子应用构建必须把libraryTarget设为umd。Vue CLI 默认出口符合要求CRA 需要额外配置。如果子应用路由用了 history 模式入口 HTML 还要保证对任意路径都返回同一个页面否则刷新就 404。到这一步机制层面的东西就齐了下一步动手搭父应用。3. 父应用改造用 Vue 基座跑通 registerMicroApps 最小流程3.1 创建基座目录并安装 qiankun基座本质上是一个 Vue 应用加一段注册逻辑。我习惯先建一个干净的工程再按职责拆目录。vue create qiankun-base cd qiankun-base npm install qiankun项目名可以随意只要接下来注册子应用时name不冲突就行。安装完成后确认package.json里已经写入 qiankun 依赖这一步卡住多半是网络源的问题换个源重装即可。目录层面我保持最小结构文件职责src/main.js注册子应用、启动沙箱、挂载全局错误回调src/App.vue导航入口 子应用挂载容器src/router/index.js为子应用路径配置占位路由3.2 registerMicroApps 注册 Vue 子应用与 React 子应用在main.js里添加注册逻辑import { registerMicroApps, start } from qiankun registerMicroApps([ { name: vue-child, entry: //localhost:7101, container: #subapp-container, activeRule: /app/vue-child }, { name: react-child, entry: //localhost:7102, container: #subapp-container, activeRule: /app/react-child } ]) start()参数说明name必须全局唯一它是沙箱命名空间和日志标识entry是子应用地址开发环境写子应用 dev server生产环境换成部署后的完整 URLcontainer是基座页面上预留 DOM 的选择器子应用最终渲染进这个节点activeRule是触发条件字符串按路径前缀匹配也可以传函数做更细粒度判断比如按 token 是否存在决定是否激活。基座的App.vue里要预留挂载节点同时放两个导航入口template div nav router-link to/app/vue-childVue 子应用/router-link router-link to/app/react-childReact 子应用/router-link /nav div idsubapp-container/div /div /template#subapp-container这个节点很关键子应用挂载和卸载都发生在这里。它不需要写任何业务样式qiankun 会在路由命中时往里面插入内容切走时再清空。基座路由要给这两个路径做占位否则 Vue Router 会报No match。占位组件内容无所谓因为真正渲染到#subapp-container里的是子应用// src/router/index.js const routes [ { path: /, redirect: /app/vue-child }, { path: /app/vue-child, component: { render: (h) h(div) } }, { path: /app/react-child, component: { render: (h) h(div) } } ]3.3 start 参数与沙箱prefetch、singular、样式隔离怎么配start()不是无脑调用参数会影响开发体验和运行行为。我一般这样配start({ prefetch: true, singular: true, sandbox: { experimentalStyleIsolation: true } })prefetch控制是否预加载未激活的子应用资源生产环境开着收益明显开发环境建议关掉否则控制台会有一堆资源请求干扰调试。singular限定同一时刻只渲染一个子应用行为简单适合刚接入的阶段等需要并行子应用时再关。sandbox.experimentalStyleIsolation会给子应用插入的样式加上作用域前缀缓解样式互相污染它和strictStyleIsolation的区别后面避坑章节展开。这里有个常见误用只调registerMicroApps不调start页面没有任何反应。注册是登记start()才是真正启动沙箱和路由监听两行缺一不可。3.4 基座的加载状态回调怎么看registerMicroApps的第二个参数是生命周期回调我至少会挂beforeLoad、afterMount、afterUnmount三个用来观察子应用每个阶段是否按预期执行registerMicroApps(apps, { beforeLoad: (app) { console.log(beforeLoad, app.name) }, afterMount: (app) { console.log(afterMount, app.name) }, afterUnmount: (app) { console.log(afterUnmount, app.name) } })回调里拿到的app对象包含name、entry、container等注册字段调试时直接打印就能确认注册信息有没有错位。比如怀疑子应用没加载看beforeLoad有没有触发如果连它都没触发问题大概率在activeRule匹配而不是子应用本身。这一层在 demo 阶段看似多余等子应用数量一多它是定位问题最快的人口。4. 子应用接入Vue 子应用与 React 子应用的差异改造4.1 Vue 子应用改写 main.js 并适配挂载容器子应用同样用 Vue CLI 创建。核心改动是把入口从「创建 App 并挂载」改成「导出一组生命周期函数独立运行时仍能自己挂载」。import Vue from vue import App from ./App.vue import router from ./router let instance null function render(props {}) { const { container } props instance new Vue({ router, render: (h) h(App) }).$mount(container ? container.querySelector(#app) : #app) } if (window.__POWERED_BY_QIANKUN__) { __webpack_public_path__ window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ } if (!window.__POWERED_BY_QIANKUN__) { render() } export async function bootstrap() {} export async function mount(props) { render(props) } export async function unmount() { instance.$destroy() instance null }代码逻辑是这样的全局变量window.__POWERED_BY_QIANKUN__由 qiankun 注入被基座加载时为 true单独打开时这个值不存在走render()直接渲染到#app从而保留独立调试能力。container是基座传入的挂载容器嵌入时 mount 要从容器里找#app再挂载避免和基座或其他子应用的节点冲突。__webpack_public_path__那段作用在第五章会详细说明。Vue 子应用最常犯的错是删除「独立运行」分支导致单独打开子应用时白屏。这会给联调带来很大麻烦因为 qiankun 报错和子应用自身报错会混在一起。注意如果子应用是 Vue 3把new Vue(...)换成createApp(App)挂载用app.mount()卸载用app.unmount()生命周期导出结构不变。4.2 React 子应用改写 index.js 并把卸载写干净React 子应用入口一般是src/index.jsimport React from react import { createRoot } from react-dom/client import App from ./App let root null function render(props {}) { const { container } props const target container?.querySelector(#root) ?? document.querySelector(#root) if (!root) { root createRoot(target) } root.render(App /) } if (window.__POWERED_BY_QIANKUN__) { __webpack_public_path__ window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ } if (!window.__POWERED_BY_QIANKUN__) { render({}) } export async function bootstrap() {} export async function mount(props) { render(props) } export async function unmount() { root?.unmount() root null }这里用的是 React 18 的createRootroot用模块级变量保存保证多次 mount 复用同一个 root 实例退出时root.unmount()销毁。如果子应用还在 React 17把createRoot换成ReactDOM.render卸载用ReactDOM.unmountComponentAtNode(target)。React 子应用最容易漏掉的是卸载清理漏掉之后切走再切回来页面里会出现重复内容而且控制台会抛出 React 的警告指向「同一个节点被多次渲染」。还有一个细节入口 JS 打包成 UMD 后__webpack_public_path__的赋值位置必须在业务 import 之后、业务代码执行之前否则部分产物路径仍按默认根路径解析。4.3 开发服务器跨域与路由 base 分组子应用 dev server 必须允许跨域qiankun 抓取 HTML 才会成功。Vue CLI 在vue.config.js里加// vue.config.js module.exports { devServer: { port: 7101, headers: { Access-Control-Allow-Origin: * } } }CRA 项目没有直接暴露devServer配置常见做法是用 craco 或 react-app-rewired 覆盖。我一般用 craco// craco.config.js module.exports { devServer: { port: 7102, headers: { Access-Control-Allow-Origin: * } } }开发环境用*没问题生产环境如果子应用资源独立部署配合域名白名单更稳妥。路由 base 要和父应用activeRule对齐。Vue 子应用路由const router new VueRouter({ mode: history, base: window.__POWERED_BY_QIANKUN__ ? /app/vue-child : /, routes })React 子应用BrowserRouter basename{window.__POWERED_BY_QIANKUN__ ? /app/react-child : /}不配 base 的表现很迷惑地址栏能切到/app/vue-child/xxx但子应用内部路由从当前位置匹配失败内容区空白且不报错。这个坑几乎每个第一次接 qiankun 的人都会遇到。补充一点如果子应用由 Vite 构建直接接入 qiankun 会碰到兼容问题因为 Vite 默认产 ESM开发服务器返回的是 import 语句和沙箱执行机制有冲突。常见做法是子应用退回 webpack 构建或使用社区适配插件这个 demo 场景下用 Vue CLI 构建是最少踩坑的路径。5. qiankun 实战避坑5 个绕不开的细节问题5.1 子应用静态资源 404publicPath 没有跟随子应用地址现象子应用页面正常渲染但图片、懒加载 chunk 全部 404控制台里资源请求的地址指向基座域名。原因子应用构建产物里的资源路径默认是/被基座加载后浏览器按基座域名补全自然找不到子应用的资源。解决在入口 JS 顶部设置运行时 publicPath。if (window.__POWERED_BY_QIANKUN__) { __webpack_public_path__ window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ }这段代码的意思是被基座加载时webpack 把资源公共路径切换为 qiankun 注入的子应用真实地址。我习惯在构建配置里也把publicPath改成相对路径或完整地址双保险。第一次接子应用我在这里翻过车调了半天只是图片路径少了一个运行时赋值页面跟被诅咒了一样。5.2 样式互相污染沙箱默认不隔离样式现象子应用进入后基座的弹窗样式变了切到另一个子应用上一个应用的表单样式残留按钮和边框明显不对。原因qiankun 默认只开 JS 沙箱样式没有隔离两个应用如果有同名类选择器后面的样式会覆盖前面的。解决从三个层面处理。第一基座start时开启experimentalStyleIsolation: trueqiankun 会给子应用插入的样式加作用域前缀。第二子应用内部用 CSS Modules 或 Vue scoped把冲突面缩小到业务代码。第三约定公共样式变量避免都去覆盖全局 body 类名。隔离模式做法代价experimentalStyleIsolation样式加作用域前缀动态插入 style 的场景适配有限strictStyleIsolationShadow DOM弹窗、全局样式穿透成本高开启样式隔离不是零成本个别动态插入 style 的场景需要额外适配这个决定要在需求评审时讲清楚不要等上线前才发现。5.3 路由白屏activeRule 和子应用 base 不匹配现象点击导航后地址栏正确子应用区域空白控制台没有明显报错。原因父应用按activeRule判定是否激活子应用但子应用自己的路由 base 没加这段前缀内部认为当前路径不属于自己渲染不出任何组件。解决让两个配置严格对齐。Vue 子应用的 router base 或 React 的 BrowserRouter basename必须和父应用activeRule的前缀一致比如父应用写/app/vue-child子应用 base 就写/app/vue-child。排查顺序我一般这样走先独立打开子应用确认可用再在 mount 里打印router.currentRoute看内部路由到底命中了什么。这个动作能省掉大量无头绪的调试时间比一行行断点高效得多。5.4 子应用崩溃把父应用带崩全局错误处理没挂现象子应用没启动点击导航后父应用页面还能操作但控制台报错刷屏有的场景直接整页渲染中断。原因子应用脚本执行抛错后错误没有被吞掉或转成用户可读提示qiankun 的启动流程中断基座后续行为不可控。解决在基座注册全局错误处理至少做到不让错误静默。import { addGlobalUncaughtErrorHandler } from qiankun addGlobalUncaughtErrorHandler((event) { const message event.message || 子应用加载失败 // 将 message 展示到页面提示区域 })回调里不要再抛异常否则会形成错误循环。生产环境我还会把 message 上报到前端监控带上子应用 name定位速度快很多。5.5 切走再切回来实例和副作用没清理干净现象从子应用 A 切到 B再切回 A页面里出现两个重复的区块或者定时器仍在执行。原因unmount 只调用了实例销毁没有清理事件监听和定时器再次 mount 时又创建了一份。解决把 unmount 当成「逆向渲染」来写。Vue 里instance.$destroy()并置空引用React 里root.unmount()每个setInterval、window.addEventListener都要在 unmount 里做对称移除。一个检查技巧在 unmount 里打印一行日志确认每次切走都执行了。如果没执行说明生命周期注册有问题后面就不用往内存泄漏方向查了。这五条是我在实际接 qiankun 项目中总结出的主要坑位每一个都对应着真实浪费过的时间提前知道能少熬几个夜。6. 状态通信与上线前验证从 demo 走向生产的最后一步6.1 用 initGlobalState 建立父子两向通信基座可以初始化一份全局状态通过initGlobalState维护跨应用数据import { initGlobalState } from qiankun const actions initGlobalState({ token: localStorage.getItem(token) || , user: null }) actions.onGlobalStateChange((state, prev) { console.log(全局状态变更, state, prev) })子应用在 mount 里拿到的 props 自带两个通信方法export async function mount(props) { props.onGlobalStateChange((state, prev) { // 子应用监听基座下发的状态 }) props.setGlobalState({ activePath: /app/vue-child }) }这套机制适合放登录态、用户信息这类跨应用数据。我会刻意保持状态扁平避免放深层嵌套对象因为全局状态的更新走浅比较嵌套对象容易触发预期之外的更新次数。6.2 上线前跑一遍验证清单冷启动检查所有子应用 dev server 关掉从基座直接访问页面有友好提示父应用自身功能不受影响。独立运行检查每个子应用单独打开确认不依赖基座也能工作if (!window.__POWERED_BY_QIANKUN__) render()这个分支不能删。资源路径检查子应用肉眼扫一遍图片、懒加载 chunk确认没有 404。样式回归抽查切换三五次子应用重点看公共类名和弹窗样式是否被互相污染。全局状态联调基座登录态变化时子应用页面内需要联动的地方有响应切走后监听不残留。我习惯把这五条固定成一张验收表每次迭代后照着走一遍比临时凭感觉点几个页面可靠得多。接入 qiankun 的头几周半数问题都出在路由 base 和样式隔离上把这两处检查前置能省掉大量联调时间。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?