微前端的核心思路是把一个大型单页应用拆分成多个可以独立开发、独立部署的小应用,再由一个主应用(也叫基座)统一管理。主应用要做的事情其实就两件:一是注册子应用,告诉系统有哪些子应用、从哪里加载、在什么路由下激活;二是路由分发,监听浏览器地址变化,把匹配到的子应用挂载到指定容器,把不匹配的卸载掉。这两件事看似简单,实际落地时会遇到路由模式冲突、base 路径设置、样式与全局变量隔离等一系列问题。本文以 Vue 3 为主应用技术栈,结合 qiankun 的使用方式,完整拆解子应用注册与路由分发的实现细节。

一、主应用的技术选型与基础准备
目前 Vue 3 生态下做微前端,主流方案有 qiankun、wujie、micro-app 几种。qiankun 基于 single-spa 封装,通过 HTML Entry 的方式加载子应用,社区资料最丰富,生产环境验证也最充分;wujie 借助 iframe 和 Web Component 实现隔离,接入成本低,适合子应用技术栈差异较大的场景;micro-app 则以 Web Component 自定义元素的方式接入,写法接近组件化。如果主应用和子应用都以 Vue 3 为主,且需要精细控制加载时机与通信,qiankun 依然是稳妥的选择,下文也以它为例展开。
准备工作分两步。第一步是安装依赖,在主应用工程中执行 npm i qiankun。第二步是确定子应用的承载容器,主应用通常会有一个专门的布局页,中间区域放置一个空的 DOM 容器,子应用的渲染结果都会挂载到这个容器内部:
<template>
<div class="main-layout">
<aside><!-- 侧边导航菜单 --></aside>
<main id="subapp-container"><!-- 子应用挂载点 --></main>
</div>
</template>需要注意的是,容器不要写在路由组件的根节点之外,否则子应用卸载时可能出现残留 DOM。同时主应用自身也有页面(比如登录页、首页看板),这些页面与子应用页面共存时,路由层级要提前规划好,避免主应用路由和子应用的激活规则互相覆盖。
二、子应用注册:从配置参数到代码落地
qiankun 提供的 registerMicroApps 是注册的核心 API,它接收两个参数:子应用配置数组和全局生命周期钩子。每个子应用配置包含三个必填字段。name 是子应用的唯一标识,后续通信、缓存都会用到它,必须保证全局唯一;entry 是子应用的访问地址,可以是开发服务器地址,也可以是部署后的线上地址;container 是挂载容器选择器,对应上面准备好的 #subapp-container;activeRule 是激活规则,当浏览器地址匹配该规则时,子应用会被加载和挂载。
import { registerMicroApps, start } from 'qiankun';
// 子应用注册信息,实际项目中通常从服务端接口拉取
const apps = [
{
name: 'user-app', // 子应用唯一标识
entry: '//localhost:7101', // 子应用入口地址
container: '#subapp-container',
activeRule: '/user', // 激活路由前缀
props: { token: 'xxx' } // 传递给子应用的数据
},
{
name: 'order-app',
entry: '//localhost:7102',
container: '#subapp-container',
activeRule: '/order',
props: { getGlobalState: () => state }
}
];
registerMicroApps(apps, {
beforeLoad: [(app) => {
console.log(`[主应用] ${app.name} 开始加载`);
return Promise.resolve();
}],
mounteSuccess: undefined,
afterMount: [(app) => {
console.log(`[主应用] ${app.name} 挂载完成`);
}],
afterUnmount: [(app) => {
console.log(`[主应用] ${app.name} 已卸载`);
}]
});
start({ prefetch: 'all' }); // 开启预加载,提升切换体验关于 activeRule 的写法有几点容易踩坑。它支持字符串、字符串数组、正则和函数四种形式,最常用的是字符串前缀匹配。这里的前缀匹配是按照路径段进行的,比如 activeRule: '/user' 会匹配 /user/list 和 /user/detail/1,但不会匹配 /userCenter,因为 userCenter 不是独立的路径段。如果两个子应用的激活规则存在包含关系,比如一个是 /order,另一个是 /order/report,建议把更具体的规则放在数组前面,qiankun 会按注册顺序匹配第一个命中的应用。
另一个进阶做法是把注册表放到服务端维护。主应用启动时先请求配置接口,拿到子应用列表后再调用 registerMicroApps,这样可以做到子应用动态上下线,不需要重新发布主应用。对于大型团队来说,这是几乎必做的改造。
三、路由分发:主应用路由与子应用如何协作
很多初次接入微前端的同学会疑惑:qiankun 已经监听了路由变化,主应用还需要自己的 Vue Router 吗?答案是需要的。主应用路由负责基础布局和独立页面(登录、404、系统设置等),而子应用内部的路由跳转由子应用自己的 Router 处理,主应用只在激活规则命中时负责把子应用拉起来。两者协作的关键在于 base 路径的协调。
主应用的路由配置中,通常用一个通配路由或空白布局页来承接子应用的地址段。这样做的目的是让主应用 Vue Router 不对 /user、/order 这些路径抛出 404,同时保证布局组件(含挂载容器)正常渲染:
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory('/'),
routes: [
{ path: '/login', component: () => import('@/views/Login.vue') },
{ path: '/', component: () => import('@/views/Home.vue') },
// 用通配路由兜底,渲染带子应用容器的布局页
{ path: '/:pathMatch(.*)*', component: () => import('@/views/SubLayout.vue') }
]
});子应用这一侧也要做配合。子应用 Vue Router 的 base 必须与主应用传入的激活规则一致,通常在挂载钩子里通过 __POWERED_BY_QIANKUN__ 判断运行环境,独立运行时用根路径,被主应用托管时使用主应用注入的 base:
// 子应用入口 main.js
import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';
import routes from './router/routes';
let app = null;
let router = null;
function render(props = {}) {
const { container, routerBase } = props;
router = createRouter({
// 主应用传入 routerBase 作为 base,独立运行时默认 /user
history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? routerBase : '/user'),
routes
});
app = createApp(App);
app.use(router);
app.mount(container ? container.querySelector('#app') : '#app');
}
// qiankun 生命周期导出
export async function bootstrap() {}
export async function mount(props) { render(props); }
export async function unmount() {
app.unmount();
app = null;
router = null;
}
// 独立运行
if (!window.__POWERED_BY_QIANKUN__) {
render();
}这套机制下,浏览器地址从 /user/list 切换到 /order/detail 时,qiankun 检测到 user-app 的激活规则不再匹配,触发它的 unmount 卸载实例,随后发现 order-app 命中,加载其资源并执行 mount。子应用内部的路由跳转(比如从列表页跳详情页)则完全由子应用 Router 接管,地址变化始终在 /user 前缀下,主应用不会重新分发,因此切换是静默且高效的。
四、常见问题排查与优化建议
子应用切换白屏或样式丢失:多数情况是容器被主应用路由的组件销毁逻辑清掉了,或者子应用卸载时把容器节点一并移除。子应用的挂载目标应该是容器内部的节点,卸载时只 unmount Vue 实例,不要操作容器 DOM。
publicPath 资源路径错误:子应用被主应用加载后,静态资源请求的基准路径会变,需要动态设置。Vue CLI 项目在 vue.config.js 中使用 publicPath: process.env.NODE_ENV === 'development' ? '/' : 'https://cdn.xxx.com/user/',Vite 项目则需要引入 vite-plugin-qiankun 或改用 wujie 接入更省心。
重复加载与性能优化:开启 start({ prefetch: 'all' }) 后,qiankun 会在浏览器空闲时预取所有子应用资源。如果子应用体积大,可以改为按需预加载,通过 loadMicroApp 手动控制加载时机,或者在首屏只预加载用户最常用的两三个应用。
样式与脚本隔离:qiankun 默认开启 JS 沙箱,但样式隔离需要在子应用侧开启 experimentalStyleIsolation 或使用 Shadow DOM 方案。公共依赖比如 Element Plus,可以在主应用设置 excludeAssetFilter 让其走外链共享,避免每个子应用都打包一份,能显著减少整体体积。
整体来看,Vue 3 主应用的微前端落地并不复杂,核心就是注册表设计、激活规则规划、base 路径协调这三件事。把注册表做成服务端可配置,把路由协作规则在团队内文档化,后续新增子应用的成本会降到极低,这也是微前端架构真正的价值所在。