工程化与框架系列(10)--微前端架构

news2025/3/3 5:27:22

微前端架构 🏗️

微前端是一种将前端应用分解成更小、更易管理的独立部分的架构模式。本文将详细介绍微前端的核心概念、实现方案和最佳实践。

微前端概述 🌟

💡 小知识:微前端的核心理念是将前端应用分解成一系列独立部署、松耦合的小型应用,每个应用可以由不同的团队使用不同的技术栈开发。

为什么需要微前端

在大型前端应用开发中,微前端架构能带来以下优势:

  1. 技术栈灵活性

    • 支持多框架共存
    • 渐进式技术迁移
    • 团队技术选择自由
    • 复用已有应用资产
  2. 团队自主性

    • 独立开发部署
    • 团队边界清晰
    • 降低协作成本
    • 提高开发效率
  3. 应用可维护性

    • 代码库规模可控
    • 模块职责单一
    • 降低耦合度
    • 简化测试和部署
  4. 性能优化空间

    • 按需加载应用
    • 独立缓存策略
    • 资源并行加载
    • 性能瓶颈隔离

实现方案详解 ⚡

基于路由的实现

// 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);
            }
        });
    }
}

最佳实践建议 ⭐

应用设计原则

  1. 独立性原则

    • 应用间松耦合
    • 独立开发部署
    • 运行时隔离
    • 故障隔离
  2. 统一规范

    • 通信协议标准
    • 路由管理规范
    • 样式命名规范
    • 错误处理机制
  3. 性能优化

    • 按需加载策略
    • 资源复用机制
    • 缓存优化方案
    • 预加载策略

开发流程建议

  1. 项目初始化
# 创建微前端项目结构
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
  1. 开发规范
// 子应用生命周期规范
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> {
        // 应用卸载
    }
}

结语 📝

微前端架构为大型前端应用开发提供了一种可扩展、可维护的解决方案。通过本文,我们学习了:

  1. 微前端的核心概念和价值
  2. 不同的实现方案及其特点
  3. 应用间通信机制的实现
  4. 样式隔离和资源加载优化
  5. 微前端的最佳实践和建议

💡 学习建议:

  1. 从小规模试点开始,逐步扩大应用范围
  2. 注重基础设施和工具链建设
  3. 建立完善的开发规范和文档
  4. 重视性能优化和用户体验
  5. 保持技术栈的适度统一

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

终身学习,共同成长。

咱们下一期见

💻

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2308763.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nacos + Dubbo3 实现微服务的Rpc调用

文章目录 概念整理基本概念概念助记前提RPC与HTTP类比RPC接口类的一些理解 实例代码主体结构父项目公共接口项目提供者项目项目结构POM文件实现配置文件实现公共接口实现程序入口配置启动项目检查是否可以注入到Nacos 消费者项目项目结构POM文件实现配置文件实现注册RPC服务类实…

算法-数据结构(图)-弗洛伊德算法复现(Floyd)

弗洛伊德算法&#xff08;Floyd-Warshall算法&#xff09;是一种用于求解所有节点对最短路径的动态规划算法&#xff0c;适用于有向图或无向图&#xff0c;且能处理带有负权边的图&#xff08;但不能有负权环&#xff09;。该算法的时间复杂度为 O(V3)O(V3)&#xff0c;其中 VV…

51c自动驾驶~合集22

我自己的原文哦~ https://blog.51cto.com/whaosoft/11870502 #自动驾驶数据闭环最前沿论文 近几年&#xff0c;自动驾驶技术的发展日新月异。从ECCV 2020的NeRF问世再到SIGGRAPH 2023的3DGS&#xff0c;三维重建走上了快速发展的道路&#xff01;再到自动驾驶端到端技术的…

基于javaweb的SpringBoot在线动漫信息平台系统设计和实现(源码+文档+部署讲解)

技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…

【Qt】MVC设计模式

目录 一、搭建MVC框架 二、创建数据库连接单例类SingleDB 三、数据库业务操作类model设计 四、control层&#xff0c;关于model管理类设计 五、view层即为窗口UI类 一、搭建MVC框架 里面的bin、lib、database文件夹以及sqlite3.h与工程后缀为.pro文件的配置与上次发的文章…

ARM 处理器平台 eMMC Flash 存储磨损测试示例

By Toradex秦海 1). 简介 目前工业嵌入式 ARM 平台最常用的存储器件就是 eMMC Nand Flash 存储&#xff0c;而由于工业设备一般生命周期都比较长&#xff0c;eMMC 存储器件的磨损寿命对于整个设备来说至关重要&#xff0c;因此本文就基于 NXP i.MX8M Mini ARM 处理器平台演示…

本地部署DeepSeek-R1(Dify发件邮箱、找回密码、空间名称修改)

Dify配置发件邮箱 DIfy默认邮箱配置为空&#xff0c;在邀请团队成员注册时是不会发送邀请链接的&#xff0c;只能通过手动复制生成的注册链接发送给对应的人去注册设置密码。 这样很麻烦&#xff0c;并且在找回密码时也接收不了邮件&#xff0c;无法重置密码。 找到本地部署…

EasyRTC:支持任意平台设备的嵌入式WebRTC实时音视频通信SDK解决方案

随着互联网技术的飞速发展&#xff0c;实时音视频通信已成为各行各业数字化转型的核心需求之一。无论是远程办公、在线教育、智慧医疗&#xff0c;还是智能安防、直播互动&#xff0c;用户对低延迟、高可靠、跨平台的音视频通信需求日益增长。 一、WebRTC与WebP2P&#xff1a;实…

数据库数据恢复—SQL Server附加数据库报错“错误 823”怎么办?

SQL Server数据库附加数据库过程中比较常见的报错是“错误 823”&#xff0c;附加数据库失败。 如果数据库有备份则只需还原备份即可。但是如果没有备份&#xff0c;备份时间太久&#xff0c;或者其他原因导致备份不可用&#xff0c;那么就需要通过专业手段对数据库进行数据恢复…

HTMLS基本结构及标签

HTML5是目前制作网页的核心技术&#xff0c;有叫超文本标记语言。 基本结构 声明部分位于文档的最前面&#xff0c;用于向浏览器说明当前文档使用HTML标准规范。 根部标签位于声明部分后&#xff0c;用于告知浏览器这是一个HTML文档。< html>表示文档开始&#xff0c;&l…

IDEA集成DeepSeek,通过离线安装解决无法安装Proxy AI插件问题

文章目录 引言一、安装Proxy AI1.1 在线安装Proxy AI1.2 离线安装Proxy AI 二、Proxy AI中配置DeepSeek2.1 配置本地部署的DeepSeek&#xff08;Ollama方式&#xff09;2.2 通过第三方服务商提供的API进行配置 三、效果测试 引言 许多开发者尝试通过安装Proxy AI等插件将AI能力…

phpstudy安装教程dvwa靶场搭建教程

GitHub - digininja/DVWA: Damn Vulnerable Web Application (DVWA) Dvwa下载地址 Windows版phpstudy下载 - 小皮面板(phpstudy) 小皮下载地址 1选择windows 版本&#xff0c;点击立即下载 下载完成&#xff0c;进行解压&#xff0c;注意不要有中文路径 点击.exe文件进行安装…

【linux】详谈 环境变量

目录 一、基本概念 二、常见的环境变量 取消环境变量 三、获取环境变量 通过代码获取环境变量 环境变量的特性 1. getenv函数:获取指定的环境变量 2. environ获取环境变量 四、本地变量 五、定义环境变量的方法 临时定义&#xff08;仅对当前会话有效&#xff09; 永…

【Linux高级IO】多路转接(poll epoll)

目录 1. poll 2. epoll 2.1 epoll_ctl 2.2 epoll_wait 2.3 epoll原理 2.4 epoll的工作模式 2.5 epoll的惊群效应 使用建议 总结 1. poll poll也是实现 I/O 多路复用的系统调用&#xff0c;可以解决select等待fd上限的问题&#xff0c;将输入输出参数分离&#xff0c;不需要…

供应链管理系统--升鲜宝门店收银系统功能解析,登录、主界面、会员 UI 设计图(一)

供应链管理系统--升鲜宝门店收银系统功能解析&#xff0c;登录、主界面 会员 UI 设计图&#xff08;一&#xff09;

【Linux系统编程】基础IO--磁盘文件

目录 前言 磁盘的机械构成 盘片介绍 盘片与磁头 数据的存储&#xff08;硬件&#xff09; 磁盘的物理存储 逻辑结构&#xff1a;磁道/柱面、扇面、扇区 磁盘I/O的基本单位与扇区的存储密度 CHS定位法&#xff1a;数据的查找 磁盘的逻辑存储 扇区的抽象结构(数据…

C# .NET Core HttpClient 和 HttpWebRequest 使用

HttpWebRequest 这是.NET创建者最初开发用于使用HTTP请求的标准类。HttpWebRequest是老版本.net下常用的&#xff0c;较为底层且复杂&#xff0c;访问速度及并发也不甚理想&#xff0c;但是使用HttpWebRequest可以让开发者控制请求/响应流程的各个方面&#xff0c;如 timeouts,…

[3/11]C#性能优化-实现 IDisposable 接口-每个细节都有示例代码

[3]C#性能优化-实现 IDisposable 接口-每个细节都有示例代码 前言 在C#开发中&#xff0c;性能优化是提升系统响应速度和资源利用率的关键环节。 当然&#xff0c;同样是所有程序的关键环节。 通过遵循下述建议&#xff0c;可以有效地减少不必要的对象创建&#xff0c;从而减…

1.C语言初识

C语言初识 C语言初识基础知识hello world数据类型变量、常量变量命名变量分类变量的使用变量的作用域 常量字符字符串转义字符 选择语句循环语句 函数&#xff1b;数组函数数组数组下标 操作符操作符算术操作符移位操作符、位操作符赋值操作符单目操作符关系操作符逻辑操作符条…

软件测试中的BUG

文章目录 软件测试的生命周期BugBug 的概念描述 Bug 的要素案例Bug 级别Bug 的生命周期与开发产生争执怎么办&#xff1f;【高频面试题】先检查自身&#xff0c;Bug 是否描述的不清楚站在用户角度考虑并抛出问题Bug 的定级要有理有据提⾼自身技术和业务水平&#xff0c;做到不仅…