微前端架构 🏗️
微前端是一种将前端应用分解成更小、更易管理的独立部分的架构模式。本文将详细介绍微前端的核心概念、实现方案和最佳实践。
微前端概述 🌟
💡 小知识:微前端的核心理念是将前端应用分解成一系列独立部署、松耦合的小型应用,每个应用可以由不同的团队使用不同的技术栈开发。
为什么需要微前端
在大型前端应用开发中,微前端架构能带来以下优势:
-
技术栈灵活性
- 支持多框架共存
- 渐进式技术迁移
- 团队技术选择自由
- 复用已有应用资产
-
团队自主性
- 独立开发部署
- 团队边界清晰
- 降低协作成本
- 提高开发效率
-
应用可维护性
- 代码库规模可控
- 模块职责单一
- 降低耦合度
- 简化测试和部署
-
性能优化空间
- 按需加载应用
- 独立缓存策略
- 资源并行加载
- 性能瓶颈隔离
实现方案详解 ⚡
基于路由的实现
// router-based.ts
interface MicroApp {
name: string;
entry: string;
container: string;
activeRule: string;
}
export class RouterBasedMicroFrontend {
private apps: MicroApp[] = [];
constructor(apps: MicroApp[]) {
this.apps = apps;
this.initializeRouter();
}
private initializeRouter(): void {
window.addEventListener('popstate', () => {
this.handleRouteChange();
});
// 初始化时加载匹配的应用
this.handleRouteChange();
}
private handleRouteChange(): void {
const path = window.location.pathname;
const app = this.apps.find(app =>
path.startsWith(app.activeRule)
);
if (app) {
this.loadApp(app);
}
}
private async loadApp(app: MicroApp): Promise<void> {
try {
// 加载应用资源
const html = await this.fetchAppHTML(app.entry);
const container = document.querySelector(app.container);
if (container) {
container.innerHTML = html;
this.executeAppScripts(app);
}
} catch (error) {
console.error(`Failed to load app ${app.name}:`, error);
}
}
private async fetchAppHTML(entry: string): Promise<string> {
const response = await fetch(entry);
return await response.text();
}
private executeAppScripts(app: MicroApp): void {
// 执行应用脚本
// 这里需要处理JS隔离等问题
}
}
// 使用示例
const microFrontend = new RouterBasedMicroFrontend([
{
name: 'app1',
entry: 'http://localhost:3001',
container: '#app1-container',
activeRule: '/app1'
},
{
name: 'app2',
entry: 'http://localhost:3002',
container: '#app2-container',
activeRule: '/app2'
}
]);
基于Web Components的实现
// web-components.ts
interface WebComponentApp {
name: string;
element: string;
url: string;
}
export class WebComponentMicroFrontend {
constructor(apps: WebComponentApp[]) {
this.registerApps(apps);
}
private registerApps(apps: WebComponentApp[]): void {
apps.forEach(app => {
this.defineCustomElement(app);
});
}
private async defineCustomElement(app: WebComponentApp): Promise<void> {
class MicroApp extends HTMLElement {
private shadow: ShadowRoot;
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'open' });
}
async connectedCallback() {
try {
const content = await this.loadAppContent(app.url);
this.shadow.innerHTML = content;
await this.executeScripts();
} catch (error) {
console.error(`Failed to load ${app.name}:`, error);
}
}
private async loadAppContent(url: string): Promise<string> {
const response = await fetch(url);
return await response.text();
}
private async executeScripts(): Promise<void> {
// 执行应用脚本,确保在Shadow DOM上下文中运行
}
}
customElements.define(app.element, MicroApp);
}
}
// 使用示例
const webComponentMicro = new WebComponentMicroFrontend([
{
name: 'app1',
element: 'micro-app1',
url: 'http://localhost:3001/app1'
},
{
name: 'app2',
element: 'micro-app2',
url: 'http://localhost:3002/app2'
}
]);
通信机制实现 🔄
事件总线
// event-bus.ts
type EventHandler = (data: any) => void;
export class EventBus {
private static instance: EventBus;
private events: Map<string, EventHandler[]>;
private constructor() {
this.events = new Map();
}
public static getInstance(): EventBus {
if (!EventBus.instance) {
EventBus.instance = new EventBus();
}
return EventBus.instance;
}
public on(event: string, handler: EventHandler): void {
if (!this.events.has(event)) {
this.events.set(event, []);
}
this.events.get(event)!.push(handler);
}
public off(event: string, handler: EventHandler): void {
if (!this.events.has(event)) return;
const handlers = this.events.get(event)!;
const index = handlers.indexOf(handler);
if (index > -1) {
handlers.splice(index, 1);
}
}
public emit(event: string, data: any): void {
if (!this.events.has(event)) return;
this.events.get(event)!.forEach(handler => {
try {
handler(data);
} catch (error) {
console.error(`Error in event handler for ${event}:`, error);
}
});
}
}
// 使用示例
const eventBus = EventBus.getInstance();
// 在应用A中订阅事件
eventBus.on('userLogin', (user) => {
console.log('User logged in:', user);
});
// 在应用B中触发事件
eventBus.emit('userLogin', {
id: 1,
name: 'John Doe'
});
状态共享
// shared-state.ts
interface StateChangeListener<T> {
(newState: T, oldState: T): void;
}
export class SharedState<T extends object> {
private state: T;
private listeners: StateChangeListener<T>[] = [];
constructor(initialState: T) {
this.state = new Proxy(initialState, {
set: (target, property, value) => {
const oldState = { ...this.state };
target[property as keyof T] = value;
this.notifyListeners(this.state, oldState);
return true;
}
});
}
public getState(): T {
return this.state;
}
public setState(partial: Partial<T>): void {
const oldState = { ...this.state };
Object.assign(this.state, partial);
this.notifyListeners(this.state, oldState);
}
public subscribe(listener: StateChangeListener<T>): () => void {
this.listeners.push(listener);
return () => {
const index = this.listeners.indexOf(listener);
if (index > -1) {
this.listeners.splice(index, 1);
}
};
}
private notifyListeners(newState: T, oldState: T): void {
this.listeners.forEach(listener => {
try {
listener(newState, oldState);
} catch (error) {
console.error('Error in state change listener:', error);
}
});
}
}
// 使用示例
interface UserState {
isLoggedIn: boolean;
user: {
id: number;
name: string;
} | null;
}
const sharedState = new SharedState<UserState>({
isLoggedIn: false,
user: null
});
// 在应用A中订阅状态变化
sharedState.subscribe((newState, oldState) => {
console.log('State changed:', { newState, oldState });
});
// 在应用B中更新状态
sharedState.setState({
isLoggedIn: true,
user: {
id: 1,
name: 'John Doe'
}
});
样式隔离方案 🎨
CSS Module Federation
// style-isolation.ts
interface StyleConfig {
prefix: string;
scope: string;
}
export class StyleIsolation {
private config: StyleConfig;
constructor(config: StyleConfig) {
this.config = config;
this.initializeStyleIsolation();
}
private initializeStyleIsolation(): void {
// 添加样式作用域
document.documentElement.setAttribute(
'data-app-scope',
this.config.scope
);
// 处理动态添加的样式
this.observeStyleChanges();
}
private observeStyleChanges(): void {
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node instanceof HTMLStyleElement) {
this.processStyle(node);
}
});
});
});
observer.observe(document.head, {
childList: true
});
}
private processStyle(styleElement: HTMLStyleElement): void {
const css = styleElement.textContent || '';
const scopedCss = this.scopeCSS(css);
styleElement.textContent = scopedCss;
}
private scopeCSS(css: string): string {
// 为所有选择器添加作用域前缀
return css.replace(/([^}]*){/g, (match) => {
return match
.split(',')
.map(selector =>
`[data-app-scope="${this.config.scope}"] ${selector.trim()}`
)
.join(',');
});
}
}
// 使用示例
const styleIsolation = new StyleIsolation({
prefix: 'app1',
scope: 'micro-app1'
});
性能优化策略 ⚡
资源加载优化
// resource-loader.ts
interface ResourceConfig {
js: string[];
css: string[];
prefetch?: string[];
}
export class ResourceLoader {
private loadedResources: Set<string> = new Set();
private loading: Map<string, Promise<void>> = new Map();
public async loadApp(config: ResourceConfig): Promise<void> {
try {
// 并行加载JS和CSS资源
await Promise.all([
this.loadJSResources(config.js),
this.loadCSSResources(config.css)
]);
// 预加载其他资源
if (config.prefetch) {
this.prefetchResources(config.prefetch);
}
} catch (error) {
console.error('Failed to load resources:', error);
throw error;
}
}
private async loadJSResources(urls: string[]): Promise<void> {
const promises = urls.map(url => this.loadJS(url));
await Promise.all(promises);
}
private async loadCSSResources(urls: string[]): Promise<void> {
const promises = urls.map(url => this.loadCSS(url));
await Promise.all(promises);
}
private async loadJS(url: string): Promise<void> {
if (this.loadedResources.has(url)) {
return;
}
if (this.loading.has(url)) {
return this.loading.get(url);
}
const promise = new Promise<void>((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.async = true;
script.onload = () => {
this.loadedResources.add(url);
this.loading.delete(url);
resolve();
};
script.onerror = () => {
this.loading.delete(url);
reject(new Error(`Failed to load script: ${url}`));
};
document.head.appendChild(script);
});
this.loading.set(url, promise);
return promise;
}
private async loadCSS(url: string): Promise<void> {
if (this.loadedResources.has(url)) {
return;
}
return new Promise((resolve, reject) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
link.onload = () => {
this.loadedResources.add(url);
resolve();
};
link.onerror = () => {
reject(new Error(`Failed to load CSS: ${url}`));
};
document.head.appendChild(link);
});
}
private prefetchResources(urls: string[]): void {
urls.forEach(url => {
if (!this.loadedResources.has(url)) {
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = url;
document.head.appendChild(link);
}
});
}
}
最佳实践建议 ⭐
应用设计原则
-
独立性原则
- 应用间松耦合
- 独立开发部署
- 运行时隔离
- 故障隔离
-
统一规范
- 通信协议标准
- 路由管理规范
- 样式命名规范
- 错误处理机制
-
性能优化
- 按需加载策略
- 资源复用机制
- 缓存优化方案
- 预加载策略
开发流程建议
- 项目初始化
# 创建微前端项目结构
mkdir micro-frontend && cd micro-frontend
mkdir container app1 app2 shared
# 初始化基座应用
cd container
npm init -y
npm install @micro-frontend/core
# 初始化子应用
cd ../app1
npm init -y
npm install @micro-frontend/app
- 开发规范
// 子应用生命周期规范
interface MicroAppLifecycle {
bootstrap(): Promise<void>;
mount(props: Record<string, any>): Promise<void>;
unmount(): Promise<void>;
}
// 实现示例
export class MicroApp implements MicroAppLifecycle {
async bootstrap(): Promise<void> {
// 应用初始化
}
async mount(props: Record<string, any>): Promise<void> {
// 应用挂载
}
async unmount(): Promise<void> {
// 应用卸载
}
}
结语 📝
微前端架构为大型前端应用开发提供了一种可扩展、可维护的解决方案。通过本文,我们学习了:
- 微前端的核心概念和价值
- 不同的实现方案及其特点
- 应用间通信机制的实现
- 样式隔离和资源加载优化
- 微前端的最佳实践和建议
💡 学习建议:
- 从小规模试点开始,逐步扩大应用范围
- 注重基础设施和工具链建设
- 建立完善的开发规范和文档
- 重视性能优化和用户体验
- 保持技术栈的适度统一
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻