微前端
微前端是一种将前端应用拆分为多个小型、独立的前端应用的架构模式,每个应用可以独立开发、部署和维护。
1. 核心概念
1.1 什么是微前端
微前端的理念源于微服务,它将大型前端应用拆分为多个小型、自治的前端应用,每个应用负责特定的功能模块。
1.2 微前端的优势
- 独立开发:每个团队可以独立开发自己的微前端应用
- 独立部署:每个微前端应用可以独立部署,无需协调
- 技术栈灵活:每个微前端应用可以使用不同的技术栈
- 可扩展性:可以轻松添加或移除微前端应用
- 故障隔离:一个微前端应用的故障不会影响其他应用
1.3 微前端的挑战
- 通信问题:微前端应用之间需要有效的通信机制
- 样式隔离:避免样式冲突
- 状态管理:共享状态的管理
- 路由协调:多个微前端应用的路由协调
- 构建和部署复杂性:需要复杂的构建和部署流程
2. 微前端架构模式
2.1 基座模式
基座模式是最常见的微前端架构模式,它包含一个主应用(基座)和多个子应用(微前端)。
核心特点
- 主应用:负责子应用的注册、加载和卸载
- 子应用:独立开发和部署的前端应用
- 通信方式:主应用和子应用之间通过事件或自定义API通信
实现示例
// 主应用注册子应用
import { registerApplication, start } from 'single-spa';
registerApplication(
'app1',
() => import('./src/app1/main.js'),
location => location.pathname.startsWith('/app1'),
{ some: 'value' }
);
registerApplication(
'app2',
() => import('./src/app2/main.js'),
location => location.pathname.startsWith('/app2')
);
start();
2.2 模块联邦
模块联邦是Webpack 5推出的新特性,允许不同的Webpack构建之间共享模块。
核心特点
- 动态远程模块:可以动态加载远程模块
- 无服务器依赖:不需要特殊的服务器支持
- 简单配置:通过Webpack配置即可实现
实现示例
// 提供方配置 (webpack.config.js)
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app1',
exposes: {
'./Button': './src/Button',
},
shared: ['react', 'react-dom'],
}),
],
};
// 消费方配置 (webpack.config.js)
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'app2',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js',
},
shared: ['react', 'react-dom'],
}),
],
};
// 消费方使用远程模块
import { lazy, Suspense } from 'react';
const Button = lazy(() => import('app1/Button'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Button />
</Suspense>
);
}
3. 微前端框架
3.1 Single-SPA
Single-SPA是一个用于构建微前端应用的JavaScript框架,它允许在同一个页面中加载多个前端框架。
核心功能
- 应用注册:注册和管理微前端应用
- 路由协调:协调多个微前端应用的路由
- 生命周期管理:管理微前端应用的加载、挂载和卸载
- 通信机制:提供微前端应用之间的通信方式
快速开始
# 安装Single-SPA CLI
npm install -g create-single-spa
# 创建Single-SPA应用
create-single-spa
3.2 Qiankun
Qiankun是一个基于Single-SPA的微前端框架,由蚂蚁金服开发,提供了更完善的功能和更好的开发体验。
核心功能
- HTML Entry:支持通过HTML入口加载微前端应用
- 样式隔离:自动处理样式隔离
- JS沙箱:提供JavaScript沙箱环境
- 预加载:支持预加载微前端应用
- 简单API:提供简洁易用的API
快速开始
// 主应用配置
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'app1',
entry: '//localhost:8080',
container: '#container',
activeRule: '/app1',
},
{
name: 'app2',
entry: '//localhost:8081',
container: '#container',
activeRule: '/app2',
},
]);
start();
// 子应用配置 (main.js)
export async function bootstrap() {
console.log('app1 bootstraped');
}
export async function mount(props) {
console.log('app1 mounted', props);
// 渲染应用
}
export async function unmount(props) {
console.log('app1 unmounted', props);
// 卸载应用
}
3.3 Piral
Piral是一个微前端框架,专注于构建可扩展的Web应用程序。
核心功能
- 插件系统:基于插件的微前端架构
- 中央状态管理:提供中央状态管理
- 路由管理:内置路由管理
- 组件共享:支持组件共享
- 开发工具:提供丰富的开发工具
4. 微前端通信
4.1 基于事件的通信
基于事件的通信是一种松耦合的通信方式,通过发布-订阅模式实现。
// 事件总线
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(callback => {
callback(data);
});
}
}
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
}
// 使用事件总线
const eventBus = new EventBus();
// 订阅事件
eventBus.on('userLoggedIn', (user) => {
console.log('User logged in:', user);
});
// 发布事件
eventBus.emit('userLoggedIn', { id: 1, name: 'Alice' });
4.2 基于props的通信
基于props的通信是主应用向子应用传递数据的简单方式。
// 主应用
registerApplication(
'app1',
() => import('./src/app1/main.js'),
location => location.pathname.startsWith('/app1'),
{ user: { id: 1, name: 'Alice' } }
);
// 子应用
export async function mount(props) {
console.log('User prop:', props.user);
}
4.3 基于自定义API的通信
基于自定义API的通信是一种更结构化的通信方式,提供明确的API接口。
// 主应用提供API
const sharedApi = {
getUser: () => ({ id: 1, name: 'Alice' }),
setUser: (user) => { /* 设置用户 */ }
};
registerApplication(
'app1',
() => import('./src/app1/main.js'),
location => location.pathname.startsWith('/app1'),
{ api: sharedApi }
);
5. 样式隔离
5.1 CSS Modules
CSS Modules通过生成唯一的类名来避免样式冲突。
/* Button.module.css */
.button {
background-color: blue;
color: white;
padding: 8px 16px;
}
import styles from './Button.module.css';
function Button() {
return <button className={styles.button}>Click me</button>;
}
5.2 Shadow DOM
Shadow DOM提供了DOM和样式的隔离。
class ShadowButton extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
<style>
button {
background-color: blue;
color: white;
padding: 8px 16px;
}
</style>
<button><slot></slot></button>
`;
}
}
customElements.define('shadow-button', ShadowButton);
5.3 CSS-in-JS
CSS-in-JS通过JavaScript动态生成样式,避免样式冲突。
import styled from 'styled-components';
const Button = styled.button`
background-color: blue;
color: white;
padding: 8px 16px;
`;
function App() {
return <Button>Click me</Button>;
}
6. 微前端路由
6.1 基于路径的路由
基于路径的路由是最常见的微前端路由方式,根据URL路径加载对应的微前端应用。
registerApplication(
'app1',
() => import('./src/app1/main.js'),
location => location.pathname.startsWith('/app1')
);
6.2 基于子域名的路由
基于子域名的路由根据子域名加载对应的微前端应用。
registerApplication(
'app1',
() => import('./src/app1/main.js'),
location => location.hostname === 'app1.example.com'
);
6.3 基于路由库的路由
使用专门的路由库来管理微前端应用的路由。
// 使用React Router
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import App1 from './App1';
import App2 from './App2';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/app1/*" element={<App1 />} />
<Route path="/app2/*" element={<App2 />} />
</Routes>
</BrowserRouter>
);
}
7. 微前端最佳实践
7.1 独立开发和部署
每个微前端应用应该有自己的代码库、构建流程和部署流程。
7.2 明确定义边界
每个微前端应用应该有明确的功能边界,避免功能重叠。
7.3 使用统一的设计系统
使用统一的设计系统确保微前端应用之间的视觉一致性。
7.4 保持技术栈的多样性
允许每个微前端应用使用最适合的技术栈,但也要考虑维护成本。
7.5 实现有效的监控
实现有效的监控系统,及时发现和解决问题。
8. 微前端案例
8.1 Spotify
Spotify使用微前端架构构建其Web应用,每个功能模块由不同的团队独立开发和部署。
8.2 IKEA
IKEA使用微前端架构构建其电子商务平台,提高了开发效率和可扩展性。
8.3 Zalando
Zalando使用微前端架构构建其时尚电商平台,支持多个团队并行开发。