微前端技术 & 应用接入
一、关于微前端
1. 什么是微前端
微前端(Micro Frontends)是一种将前端应用拆分成多个模块,每个模块可单独开发和部署的技术。

适用场景:
- 多团队协作的大型项目
- 旧项目渐进式重构
- 多产品线需要统一入口
- 核心业务模块需独立迭代
2. 微前端核心架构原则
2.1 独立开发与部署
- 技术栈自由:允许 React/Vue/Angular 混用;
- 独立仓库:每个子应用拥有独立代码库与 CI/CD 流程;
2.2 应用隔离
- CSS 隔离:Shadow DOM / 命名空间等方案;
- JS 隔离:沙箱机制防止全局污染;
- 数据隔离:独立的状态管理;
2.3 动态加载与运行时集成
主应用通过动态加载(按需加载)子应用资源(HTML/JS/CSS),并在运行时组合。
3. 微前端的实现方案
方案 | 优点 | 缺点 |
---|---|---|
路由分发式 | 简单易用 | 切换白屏/环境一致性差 |
iframe 方案 | 天然隔离 | 通信复杂/性能差 |
Web Components | 原生支持 | 生态不完善 |
通过主流框架实现 | 开箱即用/沙箱完善 | 学习成本中等 |
3.1 路由分发式
通过 Nginx 反向代理路由到不同子应用
# Nginx 配置示例
location /app1 { proxy_pass http://app1-domain.com; }
location /app2 { proxy_pass http://app2-domain.com; }
3.2 iframe 方案
<iframe src="//child-app.com" class="micro-app"></iframe>
优点:天然隔离 缺点:通信复杂、性能差、SEO不友好
3.3 Web Components
浏览器原生组件化方案
class MyElement extends HTMLElement {
connectedCallback() {
this.innerHTML = `<h1>微前端组件</h1>`;
}
}
customElements.define('micro-app', MyElement);
3.4 通过主流框架实现
框架 | qiankun | micro-app | wujie-micro |
---|---|---|---|
发布时间 | 2019-08-01 | 2021-07-09 | 2022-07-05 |
原理 | 基于 single-spa | 类 WebComponent | WebComponent + iframe |
数据通信机制 | props | addDataListener | props、window、eventBus |
IE 兼容 | ✅ | ❌ | ✅ 自动切换成 iframe |
JS 沙箱 | ✅ | ✅ | ✅ 通过 iframe 实现 JS 沙箱 |
样式隔离 | ✅ | ✅ | ✅ 通过 webcomponent 实现页面样式元素隔离 |
元素隔离 | ❌ | ✅ | ✅ |
静态资源地址补全 | ❌ | ✅ | ❌ |
预加载 | ✅ | ✅ | ✅ |
keep-alive | ❌ | ✅ | ✅ |
应用共享同一个资源 | ✅ | ✅ | ✅ |
应用嵌套 | ✅ | ✅ | ✅ |
插件系统 | ❌ | ✅ | ✅ |
子应用不改造接入 | ❌ | ✅ | ✅ 满足跨域可以不改 |
内置降级兼容处理 | ❌ | ❌ | ✅ 通过 babel 来添加 polyfill |
接入成本 | 中 | 低 | 低 |
社区成熟及活跃度 | ★★★ | ★★ | ★ |
选型建议:
- 考虑系统需要兼容 ie 浏览器场景
wujie
>qiankun
- 接入便捷度考虑
wujie
>micro-app
>qiankun
- 框架稳定性 (框架成熟度)
qiankun
>micro-app
>wujie
二、基于 qiankun 的微前端搭建
qiankun 是一个基于 single-spa 的微前端实现库,只需调用几个 qiankun 的 API 即可完成应用的微前端改造,同时由于 qiankun 的 HTML entry 及沙箱的设计,使得微应用的接入像使用 iframe 一样简单。
下面以此目录结构为例,接入 qiankun:
main-app/
├── packages/
│ └── apps/
│ ├── app1-react/
│ └── app2-vue/
├── src/
│ ├── index.js
│ ...
└── ...
1. 主应用接入
以 Create React App 生成的主应用为例:
安装依赖:
yarn add qiankun
在 App.js
中添加子应用挂载容器:
import "./App.css";
function App() {
return (
<div className="App">
<h1>主应用</h1>
{/* 子应用挂载点 */}
<div id="subapp-container"></div>
</div>
);
}
export default App;
在入口文件注册子应用:
import React from "react";
import ReactDOM from "react-dom/client";
import { registerMicroApps, start } from "qiankun";
import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
// 主应用渲染函数
function renderMainApp() {
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
}
// 注册微应用配置
const microApps = [
{
name: "app1-react", // 子应用名称(需与子应用 package.json 中的 name 一致)
entry: "//localhost:7101", // 子应用入口(开发环境地址需与子应用服务端口一致)
container: "#subapp-container", // 子应用挂载容器(需要存在于主应用 DOM 中)
activeRule: "/app1-react", // 子应用路由激活规则
},
{
name: "app2-vue",
entry: "//localhost:7102",
container: "#subapp-container",
activeRule: "/app2-vue",
},
];
// 启动配置
function initQiankun() {
// 注册微应用
registerMicroApps(microApps, {
beforeLoad: [(app) => console.log("Before load:", app.name)],
beforeMount: [(app) => console.log("Before mount:", app.name)],
afterMount: [(app) => console.log("After mount:", app.name)],
beforeUnmount: [(app) => console.log("Before unmount:", app.name)],
afterUnmount: [(app) => console.log("After unmount:", app.name)],
});
// 启动 qiankun
start({
prefetch: "all", // 预加载所有子应用
sandbox: { experimentalStyleIsolation: true }, // 开启样式隔离
});
}
// 初始化流程
function init() {
renderMainApp(); // 先渲染主应用
initQiankun(); // 再初始化 Qiankun
}
// 启动应用
init();
2. 子应用接入
2.1 React 子应用接入
以 Create React App 生成的子应用为例:
2.1.1 在配置文件适配 qiankun
如果是 webpack 搭建或已经 eject 的 React 项目,则在子应用的 webpack.config.js
中添加以下配置:
module.exports = {
output: {
library: 'app1-react', // 必须与主应用注册的 name 一致
libraryTarget: 'umd', // 必须为 UMD 格式
publicPath: process.env.NODE_ENV === 'development'
? '//localhost:7101/' // 开发环境地址(与主应用 entry 一致)
: '/app1-react/', // 生产环境地址
},
devServer: {
port: 7101, // 端口必须与主应用 entry 一致
headers: {
'Access-Control-Allow-Origin': '*' // 允许主应用跨域加载
}
}
};
如果子应用是基于 create-react-app(CRA)生成但未暴露 Webpack 配置(即没有 eject 出 webpack.config.js),可用以下方式配置:
1、安装依赖:
yarn add react-app-rewired cross-env -D
2、创建配置文件:
在子应用根目录创建 config-overrides.js:
module.exports = {
webpack: (config) => {
// 配置 UMD 格式输出
config.output.library = "app1-react";
config.output.libraryTarget = "umd";
config.output.publicPath =
process.env.NODE_ENV === "development"
? "//localhost:7101/" // 开发环境地址(与主应用 entry 一致)
: "/app1-react/"; // 生产环境地址
// 允许主应用跨域加载
config.devServer = {
...config.devServer,
port: 7101, // 与主应用 entry 端口一致
headers: {
"Access-Control-Allow-Origin": "*",
},
};
return config;
},
};
3、修改启动命令:
在 package.json 替换原有 scripts 字段:
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test"
}
4、设置环境变量配置端口:
在子应用根目录创建 .env:
PORT=7101
2.1.2 改造子应用入口文件
子应用除了在配置文件适配 qiankun 外,还需要改造入口文件,导出 Qiankun 生命周期钩子函数:
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { BrowserRouter } from "react-router-dom";
let root = null;
// 独立运行逻辑(开发环境直接渲染)
// 通过 window.__POWERED_BY_QIANKUN__ 识别是否被 Qiankun 加载
if (process.env.NODE_ENV === "development" && !window.__POWERED_BY_QIANKUN__) {
const container = document.getElementById("root");
root = ReactDOM.createRoot(container);
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
}
// 导出 Qiankun 生命周期
export async function bootstrap() {
console.log("[React] app1 bootstraped");
}
// mount() 动态挂载
// 使用主应用传入的 container 挂载子应用,避免 DOM 冲突
export async function mount(props) {
console.log("[React] mount app1", props);
const { container } = props;
// 直接使用主应用传递的 container
const dom = container ? container : document.getElementById("root");
root = ReactDOM.createRoot(dom);
root.render(
<BrowserRouter
basename={window.__POWERED_BY_QIANKUN__ ? "/app1-react" : "/"}
>
<App />
</BrowserRouter>
);
}
// unmount() 清理资源
// 确保子应用卸载时释放内存,防止内存泄漏
export async function unmount() {
console.log("[React] Unmount");
root?.unmount();
root = null;
}
// 性能监控(可选)
reportWebVitals();
子应用如果用到 react-router,需要跟上面一样设置 basename
去匹配主应用路由,否则会导致路由混乱。
2.2 Vue 子应用接入
2.2.1 在配置文件适配 qiankun
以基于 Vite 的 Vue 子应用 app2-vue 为例:
1、安装依赖:
yarn add vite-plugin-qiankun
2、修改配置文件:
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import qiankun from 'vite-plugin-qiankun'
// https://vite.dev/config/
export default defineConfig({
base: '/app2-vue/', // 需与主应用注册的子应用路径匹配
plugins: [
vue(),
vueJsx(),
qiankun('app2-vue', {
// 子应用名称(需与主应用注册名称一致)
useDevMode: true, // 开发模式启用热更新
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
server: {
port: 7102, // 指定端口(需与主应用注册的端口一致)
cors: true, // 必须开启跨域
headers: {
'Access-Control-Allow-Origin': '*', // 允许主应用跨域加载
},
},
build: {
// 打包成 UMD 格式以适配 Qiankun
lib: {
entry: 'src/main.ts',
name: 'app2-vue',
fileName: (format) => `app2-vue.${format}.js`,
},
},
})
2.2.2 改造子应用入口文件
子应用除了在配置文件适配 qiankun 外,还需要改造入口文件,导出 Qiankun 生命周期钩子函数:
import './assets/main.css'
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import type { QiankunProps } from 'vite-plugin-qiankun/dist/helper'
import App from './App.vue'
import router from './router'
let app: ReturnType<typeof createApp>
declare global {
interface Window {
__POWERED_BY_QIANKUN__?: boolean
__INJECTED_PUBLIC_PATH_BY_QIANKUN__?: string
}
}
// 判断运行环境,如果是非 qiankun 环境,则直接挂载
if (!window.__POWERED_BY_QIANKUN__) {
// 动态同步 qiankun 注入的路径到 Vite
import.meta.env.BASE_URL = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ || '/'
app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')
}
// 作为子应用时导出生命周期
export const bootstrap = async () => {
console.log('app2-vue bootstraped')
}
export const mount = async (props: QiankunProps) => {
console.log('app2-vue mount', props)
app = createApp(App)
app.use(createPinia())
app.use(router)
// 注意这里的容器 ID 需要与主应用注册时设置的 container 匹配
app.mount(props.container?.querySelector('#subapp-container') || '#subapp-container')
}
export const unmount = async () => {
app?.unmount()
}
3. 项目启用
1、启动子应用:
cd main-app/packages/apps/app1-react
yarn start
预期输出:Project is running at http://localhost:7101
2、访问子应用独立页面:
打开 http://localhost:7101
,确认子应用正常显示。
3、启动主应用:
cd main-app
yarn start
4、访问主应用并加载子应用:
打开 http://localhost:3000/app1-react
,子应用应正确挂载到主应用的 #subapp-container
容器中。
app2-vue 同理。
代码详情见 GitHub
4. 常见踩坑与解决方案
4.1 Target container is not a DOM element
运行 http://localhost:3000/app1-react
,出现以下错误:

原因:
容器选择逻辑错误,在子应用 app1-react/src/index.js 中的 mount 方法里,主应用传递的 container 本身就是目标 DOM 元素,不需要通过 container.querySelector("#subapp-container")
二次查询子容器。
解决:
修改子应用的 mount 生命周期函数,直接使用主应用传递的 container,并确保容器存在:
// ...
// mount() 动态挂载
// 使用主应用传入的 container 挂载子应用,避免 DOM 冲突
export async function mount(props) {
console.log("[React] mount app1", props);
const { container } = props;
const dom = container
? container.querySelector("#subapp-container")
: document.getElementById("subapp-container");
// 直接使用主应用传递的 container
const dom = container ? container : document.getElementById("root");
root = ReactDOM.createRoot(dom);
root.render(
<BrowserRouter
basename={window.__POWERED_BY_QIANKUN__ ? "/app1-react" : "/"}
>
<App />
</BrowserRouter>
);
}
// ...
4.2 Cannot use import statement outside a module
运行 http://localhost:3000/app2-vue
,出现以下错误:

原因:
日志的 virtual:vue-devtools-path:overlay.js
模块加载异常,该插件在开发模式下会注入虚拟模块,与 Qiankun 的脚本加载机制冲突。
解决:
移除 vite-plugin-vue-devtools
:
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vueDevTools from 'vite-plugin-vue-devtools'
import qiankun from 'vite-plugin-qiankun'
export default defineConfig({
base: '/app2-vue/',
plugins: [
vue(),
vueJsx(),
vueDevTools(),
qiankun('app2-vue', {
useDevMode: true,
}),
],
// ...其余配置保持不变
})
4.3 Failed to fetch
启动主应用和其中一个子应用 app1-react 并运行 http://localhost:3000/app1-react
,出现以下错误:

原因:
qiankun 的预加载机制尝试加载未启动的子应用导致该报错。
解决:
可以在主应用 src/index.js 中 qiankun 的 start 配置中设置 prefetch: false
,避免主应用初始化时预加载所有子应用资源:
// ...
// 启动配置
function initQiankun() {
// 注册微应用
registerMicroApps(microApps, {
beforeLoad: [(app) => console.log("Before load:", app.name)],
beforeMount: [(app) => console.log("Before mount:", app.name)],
afterMount: [(app) => console.log("After mount:", app.name)],
beforeUnmount: [(app) => console.log("Before unmount:", app.name)],
afterUnmount: [(app) => console.log("After unmount:", app.name)],
});
// 启动 qiankun
start({
prefetch: "all", // 预加载所有子应用
prefetch: false, // 关闭预加载所有子应用
sandbox: { experimentalStyleIsolation: true }, // 开启样式隔离
});
}
// 初始化流程
function init() {
renderMainApp(); // 先渲染主应用
initQiankun(); // 再初始化 Qiankun
}
// 启动应用
init();