微前端(Micro Frontend)是一种类似于微服务的架构,将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的片段,然后聚合为一个整体应用。
单应用代码量超过 10 万行,构建部署缓慢
多个团队维护同一仓库,冲突频繁
老技术无法升级,新技术无法渐进引入
业务模块需要独立迭代和发布
| 价值 | 说明 | 度量指标 |
|---|---|---|
| 技术栈无关 | 各子应用可独立选型 | 技术栈数量 |
| 独立开发 | 团队各自为政,互不阻塞 | 构建时间、冲突率 |
| 独立部署 | 按需发布,不影响全局 | 部署频率、MTTR |
| 增量升级 | 渐进式重构老系统 | 迁移周期 |
| 方案 | 技术栈 | JS 沙箱 | 样式隔离 | 通信方式 | 适用场景 |
|---|---|---|---|---|---|
| qiankun | 基于 single-spa | Proxy 沙箱 | Shadow DOM / Scope | props / Actions | 中大型应用 |
| single-spa | 框架无关 | 无 | 无 | CustomEvent | 技术栈多样 |
| Module Federation | Webpack 5 | 无 | 无 | 共享模块 | 构建时集成 |
| wujie | 基于 iframe/Shadow DOM | iframe / Proxy | Shadow DOM | props / window | 低成本接入 |
| micro-app | 基于 web component | Proxy | Shadow DOM | 属性/事件 | 轻量级 |
| 无界 (wujie) | Web Components + iframe | iframe | 天然隔离 | bus / props | 强隔离需求 |
// qiankun 核心流程 注册子应用→匹配路由→加载资源→JS 沙箱→生命周期→渲染
// main.js - 基座 import { registerMicroApps, start } from 'qiankun'; registerMicroApps([ { name: 'react-app', entry: '//localhost:3001', container: '#subapp-container', activeRule: '/react', props: { data: 'from main' } }, { name: 'vue-app', entry: '//localhost:7100', container: '#subapp-container', activeRule: '/vue', props: { data: 'from main' } } ]); start({ prefetch: 'all', sandbox: { experimentalStyleIsolation: true } });
// React 子应用入口渲染 import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; let root: ReactDOM.Root | null = null; function render(props: any) { const { container } = props; root = ReactDOM.createRoot( container ? container.querySelector('#root') : document.querySelector('#root') ); root.render(<App />); } // 独立运行时直接渲染 if (!(window as any).__POWERED_BY_QIANKUN__) { render({}); } // qiankun 生命周期钩子 export async function bootstrap() { console.log('react app bootstraped'); } export async function mount(props: any) { console.log('props from main framework', props); render(props); } export async function unmount() { root?.unmount(); root = null; }
// 子应用 webpack.config.js const { name } = require('./package.json'); module.exports = { devServer: { port: 3001, headers: { 'Access-Control-Allow-Origin': '*' }, historyApiFallback: true }, output: { library: `${name}-[name]`, libraryTarget: 'umd', chunkLoadingGlobal: `webpackJsonp_${name}`, globalObject: 'window' } };
| 模式 | 原理 | 兼容性 | 性能 |
|---|---|---|---|
| SnapshotSandbox | 快照 diff 恢复 | IE 兼容 | 差 |
| LegacySandbox | Proxy 代理 | IE 不支持 | 中 |
| ProxySandbox | 多实例 Proxy | 现代浏览器 | 好 |
// ProxySandbox 简化实现 class ProxySandbox { constructor() { this.proxy = null; this.active(); } active() { const fakeWindow = Object.create(null); const proxy = new Proxy(fakeWindow, { set: (target, prop, value) => { target[prop] = value; return true; }, get: (target, prop) => { return target[prop]; } }); this.proxy = proxy; } inactive() { // 沙箱失效,清理 } }
// remote 应用 webpack.config.js const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin'); module.exports = { plugins: [ new ModuleFederationPlugin({ name: 'remote_app', filename: 'remoteEntry.js', exposes: { './Button': './src/components/Button', './Header': './src/components/Header', }, shared: { react: { singleton: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.0.0' } } }) ] };
// host 应用 webpack.config.js module.exports = { plugins: [ new ModuleFederationPlugin({ name: 'host_app', remotes: { remote_app: 'remote_app@http://localhost:3001/remoteEntry.js' }, shared: { react: { singleton: true }, 'react-dom': { singleton: true } } }) ] }; // host 组件中使用远程组件 import RemoteButton from 'remote_app/Button'; function App() { return ( <div> <h1>Host App</h1> <RemoteButton>远程按钮</RemoteButton> </div> ); }
| 方案 | 优点 | 缺点 | 推荐度 |
|---|---|---|---|
| CSS Module | 零依赖 | 不够彻底 | ⭐⭐⭐⭐ |
| CSS-in-JS | 动态样式 | 运行时开销 | ⭐⭐⭐⭐ |
| Shadow DOM | 彻底隔离 | 兼容性问题 | ⭐⭐⭐ |
| 命名空间 | 简单 | 人为约束 | ⭐⭐⭐ |
| Style Scope | Vue 友好 | 仅限 Vue | ⭐⭐⭐⭐ |
| Dynamic Schema | 动态添加删除 | 性能开销 | ⭐⭐⭐ |
// 创建 Shadow DOM 容器 const host = document.querySelector('#subapp-container'); const shadowRoot = host.attachShadow({ mode: 'open' }); // 复制样式到 Shadow DOM const styles = document.querySelectorAll('style'); styles.forEach(style => { shadowRoot.appendChild(style.cloneNode(true)); }); // 渲染子应用到 Shadow DOM shadowRoot.appendChild(appContainer);
// 主应用发送消息 const event = new CustomEvent('global-event', { detail: { type: 'LOGIN_SUCCESS', payload: userInfo } }); window.dispatchEvent(event); // 子应用监听消息 window.addEventListener('global-event', (e) => { if (e.detail.type === 'LOGIN_SUCCESS') { store.dispatch(setUser(e.detail.payload)); } });
// 主应用创建全局 store import { initGlobalState } from 'qiankun'; const actions = initGlobalState({ user: null, theme: 'light', locale: 'zh-CN' }); actions.onGlobalStateChange((state, prev) => { console.log('主应用状态变更', prev, '→', state); }); actions.setGlobalState({ user: { name: '张三' } }); // 子应用接收 actions export async function mount(props) { props.onGlobalStateChange((state) => { // 同步状态 }); props.setGlobalState({ theme: 'dark' }); }
// 项目结构 monorepo/ ├── packages/ │ ├── main/ # 主应用 │ ├── sub-react/ # React 子应用 │ ├── sub-vue/ # Vue 子应用 │ └── shared/ # 公共依赖 ├── package.json └── lerna.json # 或 pnpm-workspace.yaml
// .gitlab-ci.yml 示例 stages: - install - build - deploy variables: NODE_VERSION: "18" install: stage: install script: - npm ci --cache .npm cache: paths: - .npm/ build_main: stage: build script: - npm run build --scope=main artifacts: paths: - packages/main/dist deploy_main: stage: deploy script: - npm run deploy --scope=main only: - main
// 创建基座 npx create-react-app main-app --template typescript npm install qiankun // src/App.tsx import { useEffect, useState } from 'react'; import { registerMicroApps, start, loadMicroApp } from 'qiankun'; function App() { const [loading, setLoading] = useState(true); useEffect(() => { registerMicroApps([ { name: 'sub-react', entry: '//localhost:3001', container: '#container', activeRule: '/sub-react' }, { name: 'sub-vue', entry: '//localhost:3002', container: '#container', activeRule: '/sub-vue' } ]); start({ prefetch: 'all', sandbox: { strictStyleIsolation: true } }); setLoading(false); }, []); return ( <div> <nav> <Link to="/sub-react">React 子应用</Link> <Link to="/sub-vue">Vue 子应用</Link> </nav> {loading && <div>加载中...</div>} <div id="container" /> </div> ); }
// 基座统一登录 class AuthService { private token: string | null = null; login(username: string, password: string) { return fetch('/api/login', { method: 'POST', body: JSON.stringify({ username, password }) }).then(res => res.json()) .then(data => { this.token = data.token; localStorage.setItem('token', data.token); return data; }); } getToken() { return this.token || localStorage.getItem('token'); } // 传递给子应用 getAuthProps() { return { token: this.getToken(), logout: () => this.logout() }; } logout() { this.token = null; localStorage.removeItem('token'); } }
微服务是后端服务拆分,通过 API 通信;微前端是前端应用拆分,通过事件/状态通信。
联系:两者都遵循单一职责原则,独立部署、独立团队、技术栈可选。
区别
| 维度 | 微服务 | 微前端 |
|---|---|---|
| 拆分粒度 | 服务级 | 应用级 |
| 通信 | HTTP/RPC | 事件/状态共享 |
| 状态 | 后端管理 | 前端管理 |
| 部署 | 容器化 | 静态资源 |