一、微前端概述
微前端(Micro Frontend)是将微服务思想应用到前端的一种架构模式:
核心理念:
- 将大型前端应用拆分为独立可部署的小应用
- 各子应用可以独立开发、测试、部署
- 技术栈无关,打破团队边界
解决的问题:
- 大型前端项目的维护困难
- 多团队协作的代码冲突
- 技术栈升级的困难
- 巨石应用的性能问题
二、微前端架构模式
1. 架构图
┌─────────────────────────────────────────────────────────────────┐
│ 微前端架构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 会员子应用 │ │ 订单子应用 │ │ 商品子应用 │ │
│ │ (React) │ │ (Vue) │ │ (Angular) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ ┌──────▼──────────────────▼──────────────────▼───────┐ │
│ │ 容器应用 │ │
│ │ (主应用骨架、路由管理、公共组件、样式隔离) │ │
│ └────────────────────────┬───────────────────────────┘ │
│ │ │
│ ┌────────────────────────▼───────────────────────────┐ │
│ │ 独立部署平台 │ │
│ │ (CI/CD流水线、版本管理、灰度发布) │ │
│ └────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
2. 技术选型对比
| 方案 | 描述 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| iframe | 嵌套独立页面 | 隔离性强 | 体验差、通讯难 | 简单场景 |
| JS Bundle | 动态加载子应用 | 体验好 | 样式冲突 | 中等复杂度 |
| Web Component | 自定义组件 | 原生隔离 | 学习曲线 | 新项目 |
| Module Federation | Webpack联邦模块 | 成熟、共享依赖 | 需Webpack | 已有项目改造 |
3. Module Federation方案
主应用配置:
javascript
// 主应用 webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const deps = require('./package.json').dependencies;
module.exports = {
mode: 'development',
devServer: {
port: 3000,
},
plugins: [
new ModuleFederationPlugin({
name: 'container',
// 共享的依赖(子应用可以复用)
shared: {
react: { singleton: true, requiredVersion: deps.react },
'react-dom': { singleton: true, requiredVersion: deps['react-dom'] }
}
})
]
};
子应用配置:
javascript
// 子应用(Vue)webpack.config.js
module.exports = {
mode: 'development',
devServer: {
port: 3001,
// 暴露子应用
headers: { 'Access-Control-Allow-Origin': '*' }
},
plugins: [
new ModuleFederationPlugin({
name: 'orderApp',
// 暴露的模块
exposes: {
'./OrderModule': './src/OrderModule'
},
// 共享依赖
shared: {
vue: { singleton: true },
'vue-router': { singleton: true }
}
})
]
};
三、微前端框架对比
1. qiankun(阿里)
特点:
- 基于single-spa
- 接入简单,API友好
- 样式隔离
- 资源预加载
主应用:
javascript
// main.js
import { registerMicroApps, start } from 'qiankun';
const apps = [
{
name: 'orderApp', // 子应用名称
entry: '//localhost:3001', // 子应用入口
container: '#container', // 挂载容器
activeRule: '/order' // 激活路由
},
{
name: 'productApp',
entry: '//localhost:3002',
container: '#container',
activeRule: '/product'
}
];
// 注册子应用
registerMicroApps(apps);
// 启动
start();
// 设置全局生命周期
import { setGlobalUnmount } from 'qiankun';
setGlobalUnmount(() => {
console.log('子应用卸载');
});
子应用(Vue):
javascript
// src/main.js
let vueApp = null;
function render(props = {}) {
const { container } = props;
vueApp = createApp(App);
vueApp.use(router);
vueApp.mount(container ? container.querySelector('#app') : '#app');
}
// qiankun生命周期
if (process.env.NODE_ENV === 'development') {
render();
}
export async function bootstrap() {
console.log('子应用启动');
}
export async function mount(props) {
console.log('子应用挂载', props);
render(props);
}
export async function unmount() {
console.log('子应用卸载');
vueApp.$destroy();
vueApp.$el.innerHTML = '';
}
2. EMP(欢聚时代)
特点:
- 基于Webpack Module Federation
- 支持多实例
- 共享依赖更高效
3. Garfish(字节)
特点:
- 支持多实例隔离
- 支持预渲染
- 完善的沙箱机制
四、微前端通信方案
1. Props通信
javascript
// 主应用传递数据
<MicroApp name="orderApp" data={{ userId: 123 }} />
// 子应用接收
function App(props) {
const { data } = props;
console.log('收到主应用数据:', data.userId);
}
2. 自定义事件
javascript
// 发送事件(子应用)
import { emit } from 'qiankun';
emit('user-login', { userId: 123, token: 'xxx' });
// 监听事件(另一个子应用)
import { onGlobalStateChange } from 'qiankun';
onGlobalStateChange((state) => {
console.log('收到全局状态变更:', state);
});
3. 共享状态(Redux/Vuex)
javascript
// shared/store/index.js
import { createStore } from 'redux';
const initialState = {
user: null,
token: null
};
function reducer(state = initialState, action) {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload };
default:
return state;
}
}
export const store = createStore(reducer);
// 主应用初始化
import { initGlobalState } from 'qiankun';
const actions = initGlobalState({ user: null, token: null });
// 监听变化
actions.onGlobalStateChange((state) => {
console.log('全局状态变化:', state);
store.dispatch({ type: 'SET_USER', payload: state.user });
});
4. 基于事件总线的通信
javascript
// eventBus.js
class EventBus {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(cb => cb(data));
}
}
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
}
export const eventBus = new EventBus();
五、样式隔离方案
1. CSS Modules
css
/* 子应用样式 */
.container {
padding: 20px;
}
.title {
color: blue;
}
/* 生成唯一的类名 */
.container { color: blue; }
.container__title { color: blue; }
2. CSS-in-JS
javascript
// 使用styled-components
import styled from 'styled-components';
const Container = styled.div`
padding: 20px;
`;
const Title = styled.h1`
color: blue;
font-size: 24px;
`;
// 样式自动隔离
3. Shadow DOM
javascript
// 使用Web Component
class OrderWidget extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'closed' });
shadow.innerHTML = `
<style>
.container { padding: 20px; }
</style>
<div class="container">
<h1>订单应用</h1>
</div>
`;
}
}
customElements.define('order-widget', OrderWidget);
4. 样式前缀隔离
javascript
// 为子应用添加唯一前缀
function mountWithPrefix(container, prefix) {
const styleElements = container.querySelectorAll('style');
styleElements.forEach(style => {
const css = style.textContent;
// 为所有选择器添加前缀
const prefixedCSS = addPrefix(css, `.${prefix}`);
style.textContent = prefixedCSS;
});
}
六、子应用加载与路由
1. 路由分发
javascript
// 主应用路由
import { createRouter, createWebHistory } from 'vue-router';
const routes = [
{
path: '/',
component: Home
},
// 动态加载子应用
{
path: '/order/:page*',
component: () => import('./OrderApp.vue')
},
{
path: '/product/:page*',
component: () => import('./ProductApp.vue')
}
];
const router = createRouter({
history: createWebHistory(),
routes
});
2. 预加载
javascript
// 主应用预加载子应用
import { preloadApp } from 'qiankun';
preloadApp([
{
name: 'orderApp',
entry: '//localhost:3001'
}
]);
3. 懒加载
javascript
// Vue Router懒加载子应用
const routes = [
{
path: '/order',
component: () => import('./views/Order.vue')
}
];
// React Router懒加载
const OrderApp = lazy(() => import('orderApp/OrderModule'));
七、微前端最佳实践
1. 项目结构
├── micro-frontend-project/
│ ├── container/ # 主应用
│ │ ├── src/
│ │ ├── public/
│ │ ├── package.json
│ │ └── webpack.config.js
│ │
│ ├── apps/
│ │ ├── order-app/ # 订单子应用
│ │ │ ├── src/
│ │ │ └── package.json
│ │ │
│ │ ├── product-app/ # 商品子应用
│ │ │ ├── src/
│ │ │ └── package.json
│ │ │
│ │ └── user-app/ # 用户子应用
│ │ ├── src/
│ │ └── package.json
│ │
│ └── shared/ # 共享代码
│ ├── components/
│ ├── utils/
│ └── styles/
2. 开发规范
javascript
// 1. 子应用导出标准接口
export { bootstrap, mount, unmount };
// 2. 统一状态管理
// 每个子应用使用独立状态,但通过事件与主应用通信
// 3. 统一错误处理
window.addEventListener('error', (e) => {
console.error('子应用错误:', e.error);
});
// 4. 统一日志
console.log('[order-app]', '用户操作日志');
3. 性能优化
javascript
// 1. 预加载关键子应用
import { prefetchApps } from 'qiankun';
prefetchApps([
{ name: 'orderApp', entry: '//localhost:3001' }
]);
// 2. 代码分割
const OrderModule = lazy(() => import('./OrderModule'));
// 3. 缓存子应用资源
// 配置CDN缓存策略
4. 安全考虑
javascript
// 1. 子应用资源校验
const ALLOWED_APPS = ['orderApp', 'productApp', 'userApp'];
function validateApp(appName, appEntry) {
if (!ALLOWED_APPS.includes(appName)) {
throw new Error('未授权的子应用');
}
}
// 2. CSP配置
// 限制子应用可以加载的资源
八、常见问题与解决方案
1. 样式冲突
css
/* 方案1:CSS命名空间 */
.order-app .container { }
.product-app .container { }
/* 方案2:CSS Modules */
:local(.container) { }
/* 方案3:Shadow DOM */
2. 状态共享
javascript
// 共享用户信息
const sharedState = {
user: null,
permissions: []
};
// 主应用设置
function setUser(user) {
sharedState.user = user;
emitGlobalStateChange(sharedState);
}
// 子应用获取
onGlobalStateChange((state) => {
sharedState.user = state.user;
});
3. 公共依赖
javascript
// webpack配置共享依赖
shared: {
react: { singleton: true, eager: true },
'react-dom': { singleton: true, eager: true }
}
4. 子应用通信
javascript
// 使用qiankun的initGlobalState
import { initGlobalState } from 'qiankun';
const initialState = {};
const actions = initGlobalState(initialState);
// 主应用监听
actions.onGlobalStateChange((state) => {
console.log('状态变化:', state);
});
// 子应用修改
actions.setGlobalState({ token: 'xxx' });
九、总结
微前端是大型前端项目的解决方案:
- 独立开发:各子应用可独立开发
- 独立部署:解除团队耦合
- 技术多样:支持不同技术栈
- 渐进迁移:逐步改造旧项目
最佳实践:
- 优先使用qiankun或Module Federation
- 做好样式隔离
- 设计好通信机制
- 统一开发规范
个人观点,仅供参考