工程化与框架系列(35)--前端微服务架构实践

前端微服务架构实践 🏗️

引言

随着前端应用规模的不断扩大,微服务架构在前端领域的应用越来越广泛。本文将深入探讨前端微服务架构的实现方案、最佳实践和相关工具。

微服务架构概述

前端微服务架构主要包括以下方面:

  • 应用拆分:基于业务域的应用拆分策略
  • 独立部署:各个微应用的独立开发、构建和部署
  • 运行时集成:微应用的加载、通信和生命周期管理
  • 共享资源:公共依赖、组件库、工具函数等的共享策略
  • 统一管理:路由、状态、权限等的统一管理方案

微服务架构实现

微前端容器

typescript 复制代码
// 微前端容器类
class MicroFrontendContainer {
    private static instance: MicroFrontendContainer;
    private apps: Map<string, MicroApp>;
    private config: ContainerConfig;
    
    private constructor() {
        this.apps = new Map();
        this.config = {
            sandbox: true,
            prefetch: true,
            isolation: 'iframe',
            timeout: 3000
        };
    }
    
    // 获取单例实例
    static getInstance(): MicroFrontendContainer {
        if (!MicroFrontendContainer.instance) {
            MicroFrontendContainer.instance = new MicroFrontendContainer();
        }
        return MicroFrontendContainer.instance;
    }
    
    // 注册微应用
    registerApp(appConfig: MicroAppConfig): void {
        const app = new MicroApp(appConfig);
        this.apps.set(appConfig.name, app);
        
        // 预加载配置
        if (this.config.prefetch) {
            this.prefetchApp(app);
        }
    }
    
    // 启动微应用
    async startApp(name: string): Promise<void> {
        const app = this.apps.get(name);
        if (!app) {
            throw new Error(`App ${name} not found`);
        }
        
        try {
            // 加载微应用资源
            await this.loadApp(app);
            
            // 创建沙箱环境
            const sandbox = this.createSandbox(app);
            
            // 挂载微应用
            await this.mountApp(app, sandbox);
            
            // 初始化通信
            this.initCommunication(app);
            
        } catch (error) {
            console.error(`Failed to start app ${name}:`, error);
            throw error;
        }
    }
    
    // 停止微应用
    async stopApp(name: string): Promise<void> {
        const app = this.apps.get(name);
        if (!app) {
            throw new Error(`App ${name} not found`);
        }
        
        try {
            // 卸载微应用
            await this.unmountApp(app);
            
            // 清理沙箱
            this.cleanupSandbox(app);
            
            // 清理资源
            this.cleanupResources(app);
            
        } catch (error) {
            console.error(`Failed to stop app ${name}:`, error);
            throw error;
        }
    }
    
    // 预加载微应用
    private async prefetchApp(app: MicroApp): Promise<void> {
        try {
            const resources = await this.loadResources(app.config.entry);
            app.setResources(resources);
        } catch (error) {
            console.warn(`Failed to prefetch app ${app.config.name}:`, error);
        }
    }
    
    // 加载微应用资源
    private async loadApp(app: MicroApp): Promise<void> {
        if (app.isLoaded()) {
            return;
        }
        
        const resources = app.getResources() || 
                         await this.loadResources(app.config.entry);
                         
        await this.injectResources(resources);
        app.setLoaded(true);
    }
    
    // 加载资源
    private async loadResources(entry: string): Promise<AppResources> {
        const response = await fetch(entry);
        const html = await response.text();
        
        return {
            scripts: this.extractScripts(html),
            styles: this.extractStyles(html),
            templates: this.extractTemplates(html)
        };
    }
    
    // 注入资源
    private async injectResources(resources: AppResources): Promise<void> {
        // 注入样式
        await Promise.all(
            resources.styles.map(style => this.loadStyle(style))
        );
        
        // 注入脚本
        await Promise.all(
            resources.scripts.map(script => this.loadScript(script))
        );
    }
    
    // 创建沙箱环境
    private createSandbox(app: MicroApp): Sandbox {
        if (this.config.isolation === 'iframe') {
            return new IframeSandbox(app);
        } else {
            return new JsSandbox(app);
        }
    }
    
    // 挂载微应用
    private async mountApp(
        app: MicroApp,
        sandbox: Sandbox
    ): Promise<void> {
        const mountPoint = document.querySelector(app.config.container);
        if (!mountPoint) {
            throw new Error(
                `Mount point ${app.config.container} not found`
            );
        }
        
        // 执行生命周期钩子
        await app.beforeMount();
        
        // 在沙箱中执行挂载
        await sandbox.mount(mountPoint);
        
        // 更新应用状态
        app.setMounted(true);
        
        // 执行生命周期钩子
        await app.afterMount();
    }
    
    // 卸载微应用
    private async unmountApp(app: MicroApp): Promise<void> {
        if (!app.isMounted()) {
            return;
        }
        
        // 执行生命周期钩子
        await app.beforeUnmount();
        
        // 移除DOM
        const container = document.querySelector(app.config.container);
        if (container) {
            container.innerHTML = '';
        }
        
        // 更新应用状态
        app.setMounted(false);
        
        // 执行生命周期钩子
        await app.afterUnmount();
    }
    
    // 清理沙箱
    private cleanupSandbox(app: MicroApp): void {
        const sandbox = app.getSandbox();
        if (sandbox) {
            sandbox.cleanup();
        }
    }
    
    // 清理资源
    private cleanupResources(app: MicroApp): void {
        const resources = app.getResources();
        if (resources) {
            // 移除样式
            resources.styles.forEach(style => {
                const element = document.querySelector(
                    `link[href="${style}"]`
                );
                element?.remove();
            });
            
            // 移除脚本
            resources.scripts.forEach(script => {
                const element = document.querySelector(
                    `script[src="${script}"]`
                );
                element?.remove();
            });
        }
    }
    
    // 初始化应用间通信
    private initCommunication(app: MicroApp): void {
        const eventBus = EventBus.getInstance();
        
        // 注册应用通信处理器
        app.setMessageHandler(message => {
            eventBus.emit(`${app.config.name}:message`, message);
        });
        
        // 监听其他应用消息
        this.apps.forEach(otherApp => {
            if (otherApp !== app) {
                eventBus.on(
                    `${otherApp.config.name}:message`,
                    message => {
                        app.postMessage(message);
                    }
                );
            }
        });
    }
}

// 微应用类
class MicroApp {
    private loaded: boolean = false;
    private mounted: boolean = false;
    private resources: AppResources | null = null;
    private sandbox: Sandbox | null = null;
    private messageHandler: MessageHandler | null = null;
    
    constructor(public config: MicroAppConfig) {}
    
    // 生命周期钩子
    async beforeMount(): Promise<void> {
        await this.invokeLifecycle('beforeMount');
    }
    
    async afterMount(): Promise<void> {
        await this.invokeLifecycle('afterMount');
    }
    
    async beforeUnmount(): Promise<void> {
        await this.invokeLifecycle('beforeUnmount');
    }
    
    async afterUnmount(): Promise<void> {
        await this.invokeLifecycle('afterUnmount');
    }
    
    // 调用生命周期函数
    private async invokeLifecycle(name: string): Promise<void> {
        const lifecycle = (window as any)[
            `${this.config.name}:${name}`
        ];
        if (typeof lifecycle === 'function') {
            await lifecycle();
        }
    }
    
    // 状态管理
    isLoaded(): boolean {
        return this.loaded;
    }
    
    setLoaded(loaded: boolean): void {
        this.loaded = loaded;
    }
    
    isMounted(): boolean {
        return this.mounted;
    }
    
    setMounted(mounted: boolean): void {
        this.mounted = mounted;
    }
    
    // 资源管理
    getResources(): AppResources | null {
        return this.resources;
    }
    
    setResources(resources: AppResources): void {
        this.resources = resources;
    }
    
    // 沙箱管理
    getSandbox(): Sandbox | null {
        return this.sandbox;
    }
    
    setSandbox(sandbox: Sandbox): void {
        this.sandbox = sandbox;
    }
    
    // 消息通信
    setMessageHandler(handler: MessageHandler): void {
        this.messageHandler = handler;
    }
    
    postMessage(message: any): void {
        this.messageHandler?.(message);
    }
}

// 沙箱基类
abstract class Sandbox {
    constructor(protected app: MicroApp) {}
    
    abstract mount(container: Element): Promise<void>;
    abstract cleanup(): void;
}

// iframe沙箱
class IframeSandbox extends Sandbox {
    private iframe: HTMLIFrameElement | null = null;
    
    async mount(container: Element): Promise<void> {
        this.iframe = document.createElement('iframe');
        this.iframe.src = 'about:blank';
        this.iframe.style.width = '100%';
        this.iframe.style.height = '100%';
        this.iframe.style.border = 'none';
        
        container.appendChild(this.iframe);
        
        // 注入资源到iframe
        await this.injectResources();
    }
    
    cleanup(): void {
        this.iframe?.remove();
        this.iframe = null;
    }
    
    private async injectResources(): Promise<void> {
        if (!this.iframe) return;
        
        const resources = this.app.getResources();
        if (!resources) return;
        
        const doc = this.iframe.contentDocument;
        if (!doc) return;
        
        // 注入样式
        resources.styles.forEach(style => {
            const link = doc.createElement('link');
            link.rel = 'stylesheet';
            link.href = style;
            doc.head.appendChild(link);
        });
        
        // 注入脚本
        for (const script of resources.scripts) {
            await new Promise((resolve, reject) => {
                const scriptElement = doc.createElement('script');
                scriptElement.src = script;
                scriptElement.onload = resolve;
                scriptElement.onerror = reject;
                doc.head.appendChild(scriptElement);
            });
        }
    }
}

// JS沙箱
class JsSandbox extends Sandbox {
    private proxy: Window | null = null;
    
    async mount(container: Element): Promise<void> {
        // 创建代理对象
        this.proxy = new Proxy(window, {
            get: (target, property) => {
                // 处理特殊属性
                if (this.isProtected(property)) {
                    return target[property as keyof Window];
                }
                
                // 返回沙箱中的值
                return (this.app as any)[property];
            },
            set: (target, property, value) => {
                // 禁止修改保护属性
                if (this.isProtected(property)) {
                    return false;
                }
                
                // 设置值到沙箱
                (this.app as any)[property] = value;
                return true;
            }
        });
        
        // 在沙箱环境中执行代码
        this.executeInSandbox(() => {
            const resources = this.app.getResources();
            if (!resources) return;
            
            // 执行脚本
            resources.scripts.forEach(script => {
                const scriptElement = document.createElement('script');
                scriptElement.src = script;
                container.appendChild(scriptElement);
            });
        });
    }
    
    cleanup(): void {
        this.proxy = null;
    }
    
    private executeInSandbox(code: Function): void {
        if (!this.proxy) return;
        
        // 保存原始window
        const originalWindow = window;
        
        // 替换为代理对象
        (window as any) = this.proxy;
        
        try {
            // 执行代码
            code();
        } finally {
            // 恢复原始window
            (window as any) = originalWindow;
        }
    }
    
    private isProtected(property: string | symbol): boolean {
        // 保护的全局属性列表
        const protectedProps = [
            'window',
            'document',
            'location',
            'history'
        ];
        
        return protectedProps.includes(property.toString());
    }
}

// 事件总线
class EventBus {
    private static instance: EventBus;
    private handlers: Map<string, Set<Function>>;
    
    private constructor() {
        this.handlers = new Map();
    }
    
    static getInstance(): EventBus {
        if (!EventBus.instance) {
            EventBus.instance = new EventBus();
        }
        return EventBus.instance;
    }
    
    on(event: string, handler: Function): void {
        if (!this.handlers.has(event)) {
            this.handlers.set(event, new Set());
        }
        
        this.handlers.get(event)?.add(handler);
    }
    
    off(event: string, handler: Function): void {
        this.handlers.get(event)?.delete(handler);
    }
    
    emit(event: string, data?: any): void {
        this.handlers.get(event)?.forEach(handler => {
            handler(data);
        });
    }
}

// 接口定义
interface ContainerConfig {
    sandbox: boolean;
    prefetch: boolean;
    isolation: 'iframe' | 'js';
    timeout: number;
}

interface MicroAppConfig {
    name: string;
    entry: string;
    container: string;
    props?: Record<string, any>;
}

interface AppResources {
    scripts: string[];
    styles: string[];
    templates: string[];
}

type MessageHandler = (message: any) => void;

// 使用示例
const container = MicroFrontendContainer.getInstance();

// 注册微应用
container.registerApp({
    name: 'app1',
    entry: 'http://localhost:3001',
    container: '#app1'
});

container.registerApp({
    name: 'app2',
    entry: 'http://localhost:3002',
    container: '#app2'
});

// 启动微应用
await container.startApp('app1');

// 停止微应用
await container.stopApp('app1');

最佳实践与建议

  1. 应用拆分

    • 基于业务域划分
    • 合理粒度
    • 独立演进
    • 技术栈灵活
  2. 依赖管理

    • 共享依赖
    • 版本控制
    • 构建优化
    • 运行时加载
  3. 通信机制

    • 事件总线
    • 状态共享
    • 数据隔离
    • 安全控制
  4. 部署策略

    • 独立部署
    • 灰度发布
    • 版本控制
    • 回滚机制

总结

前端微服务架构需要考虑以下方面:

  1. 应用拆分和集成策略
  2. 资源加载和性能优化
  3. 应用通信和状态管理
  4. 部署和运维支持
  5. 开发和协作流程

通过合理的微服务架构设计,可以提高前端应用的可维护性和扩展性。

学习资源

  1. 微前端架构设计
  2. 模块联邦实践
  3. 沙箱隔离方案
  4. 性能优化指南
  5. 部署运维实践

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

相关推荐
Anlici13 分钟前
跨域解决方案还有优劣!?
前端·面试
庸俗今天不摸鱼19 分钟前
【万字总结】构建现代Web应用的全方位性能优化体系学习指南(二)
前端·性能优化·webp
追寻光38 分钟前
Java 绘制图形验证码
java·前端
前端snow39 分钟前
爬取数据利用node也行,你知道吗?
前端·javascript·后端
村头一颗草1 小时前
高德爬取瓦片和vue2使用
前端·javascript·vue.js
远山无期1 小时前
vue3+vite项目接入qiankun微前端关键点
前端·vue.js
陈随易1 小时前
告别Node.js:2025年,我为何全面拥抱Bun
前端·后端·程序员
还是鼠鼠1 小时前
Node.js--exports 对象详解:用法、示例与最佳实践
前端·javascript·vscode·node.js·web
CQU_JIAKE1 小时前
2.5[frontEnd]
前端
Moment1 小时前
前端性能指标 —— FMP
前端·javascript·面试