从乾坤到无界:一份可落地的微前端 Demo 笔记
本文对比了两种主流微前端框架乾坤和无界的特点与实现方案。乾坤基于JS Entry+HTML Entry双模式,提供样式隔离和JS沙箱,但存在隔离不彻底的问题;无界采用Web Components+iframe混血方案,实现绝对隔离但内存占用较高。文章提供了两种框架的具体接入步骤:乾坤需要主应用注册子应用配置,子应用改造main.ts和vite配置;无界则通过wujie-vue3组件实现快速接入。关
·
从乾坤到无界:一份可落地的微前端 Demo 笔记
—— 含双框架对比、踩坑记录与源码仓库
微前端入门速览
- 为什么需要微前端当巨石应用膨胀到“编译 5 min、上线 3 h、回滚 1 h”时,就把“技术栈升级”“独立部署”“灰度发布”变成了奢望。微前端把单体拆成多个可独立开发、测试、部署的子应用,再在一个外壳(主应用)里动态拼装,兼顾了“体验一体化”与“团队自治”。
- 核心诉求技术栈无关 | 独立部署 | 运行时隔离 | 存量平滑迁移 | 性能可接受一句话:让“多团队 + 多技术栈 + 频繁发版”不再互相伤害。
- 主流实现路线① 路由分发:nginx 或主应用按路由转发到不同站点(最早、最简单,但体验割裂)。② iframe 彻底隔离:DOM/CSS/JS 天然沙箱,但“弹窗全屏、前进后退、SEO、性能”全是坑。③ JS Entry / 快照:single-spa、乾坤、无界等,把子应用打成 JS Bundle,运行时动态挂载/卸载,兼顾体验与隔离。④ Web Component + Module Federation:更原生、更底层,但浏览器支持度和改造成本需评估。
- 乾坤(qiankun)速描
- 阿里开源,single-spa 的“国内增强版”。
- 基于 JS Entry + HTML Entry 双模式,支持 webpack、vite、umi。
- 提供样式隔离(experimentalStyleIsolation)、JS 沙箱(ProxySandbox/LegacySandbox)、全局变量 diff、预加载、资源缓存、全局错误捕获。
- 社区庞大,中文文档友好,但“样式隔离不彻底”“vite 接入需插件”“IE11 下沙箱性能”仍常被吐槽。
- 无界(wujie)速描
- 腾讯开源,Web Components + iframe 的“混血”方案。
- 子应用跑在 iframe 里,DOM 通过 Web Component 插回主应用,既利用 iframe 的绝对隔离,又解决“弹窗/全屏/路由同步”顽疾。
- 支持 vite、webpack、angular、react、vue 几乎零改造;子应用可“热插拔”而不刷新整页。
- 代价:内存占用略高、初次加载多一次 iframe 创建、IE 直接放弃。
- 双框架 30 秒对比隔离强度:无界 > 乾坤接入改造成本:无界 < 乾坤社区/文档:乾坤 >> 无界首屏性能:乾坤略优(无 iframe 开销)多实例共存:无界天然支持,乾坤需手动防冲突浏览器下限:乾坤可降 IE11,无界需 Modern Chrome/Edge
- 落地小贴士
- 先画“子应用拆分图”:按业务域 > 团队边界 > 页面维度逐级拆,别一上来就拆组件。
- 统一“基座协议”:路由前缀、全局状态、错误码、登录态、灰度 KEY。
- 把“构建、部署、监控、回滚”做成模板,让业务团队只关心自己目录。
- 给子应用留“逃生窗口”:万一框架出故障,nginx 转发回独立域名仍可跑。
- 性能预算:子应用首屏 < 200 KB(gzip),基座 < 100 KB, prefetch 用 IntersectionObserver 懒触发。
- 监控:子应用白屏、JS Error、资源 404、卸载残留都要上报,否则“互相甩锅”无尽头。
qiankun-demo
使用 qiankun 实现微前端
qiankun 文档:https://qiankun.umijs.org/zh
主应用
安装依赖
npm i qiankun -S
子应用配置
在 main.ts 中加上子应用相关的配置
registerMicroApps([
{
name: "sub-app",
entry: "//localhost:5174",
container: "#sub-app",
activeRule: "/subApp",
props: {
routerBase: "/subApp",
},
},
]);
start({
sandbox: {
strictStyleIsolation: false,
experimentalStyleIsolation: true,
},
});
子应用
安装依赖
npm i vite-plugin-qiankun -D
改造 main.ts
import "./assets/main.css";
import { createApp, type App as VueApp } from "vue";
import App from "./App.vue";
import { handleCreateRouter } from "./router";
import { renderWithQiankun, qiankunWindow } from "vite-plugin-qiankun/dist/helper";
let app: VueApp | null = null;
// 渲染函数
const render = (props: any = {}) => {
const { container, routerBase } = props;
app = createApp(App);
app.use(handleCreateRouter(routerBase));
app.mount(container ? container.querySelector("#app") : "#app");
};
renderWithQiankun({
bootstrap() {
console.log("Vue3 微应用 bootstrap");
},
mount(props) {
console.log("props", props);
render(props);
},
update() {
console.log("Vue3 微应用 update");
},
unmount() {
console.log("Vue3 微应用 unmount");
app?.unmount();
app = null;
},
});
// 独立运行时
if (!qiankunWindow.__POWERED_BY_QIANKUN__) {
render();
}
改造 vite.config.ts
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueDevTools from "vite-plugin-vue-devtools";
import qiankun from "vite-plugin-qiankun";
const pkg = require("./package.json");
const appName = pkg.name;
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
// vueDevTools(),
qiankun(appName, {
useDevMode: true, // 开发环境强制启用
}),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
server: {
cors: true,
allowedHosts: true,
headers: {
"Access-Control-Allow-Origin": "*",
},
},
});
注意点
- 子应用的样式会影响主应用
- 我的主应用开启了样式隔离,但是没啥效果,不确定是就是这种设定,或者我写的有问题,又或是 vue3+vite 版本不支持
- 所有全局样式尽量保持一致,使用的 UI 组件库版本尽量也保持一致
- 主应用开启样式隔离代码:
start({ sandbox: { strictStyleIsolation: false, experimentalStyleIsolation: true, }, });
无界微前端
无界微前端示例代码 lord-app(主应用)、sub-app(子应用)
官网文档:https://wujie-micro.github.io/doc/
主应用
安装依赖
npm i wujie-vue3 -S
在主应用中注册无界
import WujieVue from "wujie-vue3";
app.use(WujieVue); // 注册无界组件
在组件中使用 WujieVue
views/SubApp.vue
<template>
<WujieVue
width="100%"
height="100%"
name="sub-app"
:url="subAppUrl"
:sync="true"
:props="subProps"
@before-load="beforeLoad"
@after-mount="afterMount"></WujieVue>
</template>
<script setup lang="ts">
import { ref } from "vue";
// 主应用地址
const subAppUrl = ref("http://localhost:5174");
// 传给子应用的数据
const subProps = ref({
baseUrl: "/subApp",
token: "Bearer 1234567890",
});
// 子应用加载前
const beforeLoad = () => {
console.log("子应用加载前");
};
// 子应用加载完成
const afterMount = () => {
console.log("子应用挂载完成");
};
</script>
<style scoped></style>
需要在路由中增加一个
{
path: "/subApp",
name: "subApp",
component: () => import("@/views/SubApp.vue"),
},
子应用
子应用需要改造一点内容
改造 main.ts
需要根据 POWERED_BY_WUJIE 判断环境,抽离出了渲染执行的函数
import "./assets/main.css";
import { createApp } from "vue";
import App from "./App.vue";
import { createRouterInstance } from "./router";
let app: ReturnType<typeof createApp>;
const render = (props: any = {}) => {
let { container, baseUrl } = props;
app = createApp(App);
app.use(createRouterInstance(baseUrl || "/"));
app.mount(container ? container.querySelector("#app") : "#app");
};
// 判断是否运行在无界环境中
if ((window as any).__POWERED_BY_WUJIE__) {
// 声明 mount 函数,供无界在适当时机调用
(window as any).__WUJIE_MOUNT = () => {
const props = (window as any).$wujie?.props;
render(props);
};
// 声明 unmount 函数,供无界在适当时机调用
(window as any).__WUJIE_UNMOUNT = () => {
app?.unmount();
};
} else {
// 正常启动
render();
}
改造路由
需要给路由加上前缀
import { createRouter, createWebHistory } from "vue-router";
import HomeView from "../views/HomeView.vue";
let routerInstance: ReturnType<typeof createRouter> | null = null;
// 获取路由示例
export function getRouter() {
if (!routerInstance) {
throw new Error("[Router] 路由实例未初始化!请在 main.ts 中先调用 createRouterInstance()");
}
return routerInstance;
}
// 工厂函数
export function createRouterInstance(basePath: string = "/") {
// 单例模式:防止重复创建
if (routerInstance) {
console.warn("[Router] 路由实例已存在,返回现有实例");
return routerInstance;
}
const router = createRouter({
history: createWebHistory(basePath),
routes: [
{
path: "/",
name: "home",
component: HomeView,
},
{
path: "/about",
name: "about",
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import("../views/AboutView.vue"),
},
],
});
// 注册守卫
// 缓存实例
routerInstance = router;
return router;
}
改造 vite.config.ts
本地调试需要开启 cors 或者 headers 请求头 Access-Control-Allow-Origin 改成*
后续发布到正式服务器还是得设置这种
server: {
cors: true,
headers: {
"Access-Control-Allow-Origin": "*",
},
},
注意点
- 关闭浏览器的
Vue.js devtools- 如果没有关闭会出现报错,Vue DevTools 在多应用环境下重复定义全局钩子
Uncaught TypeError: Cannot redefine property: __VUE_DEVTOOLS_GLOBAL_HOOK__ at Object.defineProperty (<anonymous>) at detectIframeApp (<anonymous>:33:10) at <anonymous>:69:3
- 如果没有关闭会出现报错,Vue DevTools 在多应用环境下重复定义全局钩子
更多推荐

所有评论(0)