当前位置: 首页 > news >正文

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

微前端架构 🏗️

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

微前端概述 🌟

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

为什么需要微前端

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

  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. 保持技术栈的适度统一

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

终身学习,共同成长。

咱们下一期见

💻

相关文章:

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

微前端架构 &#x1f3d7;️ 微前端是一种将前端应用分解成更小、更易管理的独立部分的架构模式。本文将详细介绍微前端的核心概念、实现方案和最佳实践。 微前端概述 &#x1f31f; &#x1f4a1; 小知识&#xff1a;微前端的核心理念是将前端应用分解成一系列独立部署、松耦…...

【3天快速入门WPF】11-附加属性

目录 1. 步骤1:定义附加属性2. 示例代码3. 步骤2:在XAML中使用附加属性3.1. 示例代码4. 步骤3:扩展使用场景4.1. 示例代码5. 总结上一篇讲到了依赖属性,本篇主要想说一下附加属性。 在WPF中,附加属性(Attached Property)是一种特殊的依赖属性,允许你在不属于某个类的控…...

MySQL并发知识(面试高频)

mysql并发事务解决 不同隔离级别下&#xff0c;mysql解决并发事务的方式不同。主要由锁机制和MVCC(多版本并发控制)机制来解决并发事务问题。 1. mysql中的锁有哪些&#xff1f; 表级锁&#xff1a; 场景&#xff1a;表级锁适用于需要对整个表进行操作的情况&#xff0c;例如…...

现存脑容知识库

Redis import queue import threading import asyncio 异步&#xff1a;在一个线程内&#xff0c;等待的时候可以切换到其他任务。 多线程&#xff1a;每个线程独立运行&#xff0c;同时处理多个任务。 回调函数 网络请求&#xff08;JavaScript&#xff09;在浏览器中&a…...

Mysql-如何理解事务?

一、事务是什么东西 有些场景中&#xff0c;某个操作需要多个sql配合完成&#xff1a; 例如&#xff1a; 李四这个月剩下的前不够交房租了&#xff0c;找张三借1000元急用&#xff1a; &#xff08;1&#xff09;给张三的账户余额 减去1000元 updata 账户表 set money money -…...

dify绑定飞书多维表格

dify 绑定飞书和绑定 notion 有差不多的过程&#xff0c;都需要套一层应用的壳子&#xff0c;而没有直接可以访问飞书文档的 API。本文记录如何在dify工具中使用新增多条记录工具。 创建飞书应用 在飞书开放平台创建一个应用&#xff0c;个人用户创建企业自建应用。 自定义应…...

QT播放视频保持视频宽高比消除黑边

QT播放视频保持视频宽高比消除黑边 1、问题 在播放视频的时候&#xff0c;由于框架的大小发生变化&#xff0c;导致视频出现黑边很不好看。 因此需要像一种方法消除黑边 2、处理 1、读取视频的宽高比 2、设置视频的Widget的大小固定&#xff0c;Widget的宽高比和视频宽高比…...

1. IO的基础知识

1.1 流 Java程序通过流执行IO。流是一种抽象&#xff0c;它要么生成信息&#xff0c;要么使用信息。流通过java的IO系统链接到物理设备。所有流的行为方式都是相同的&#xff0c;尽管它们链接的物理设备是不同的。 1.2 字节流和字符流 Java定义了两种类型的流 : 字节流和字符流…...

科普:ROC AUC与PR AUC

在评价二分类模型性能时&#xff0c;有许多评价指标&#xff0c;其中&#xff0c;有一对是用面积AUC&#xff08;Area Under the Curve&#xff09;做评价的&#xff1a;ROC AUC与PR AUC 本文我们对ROC AUC与PR AUC进行多维度对比分析&#xff1a; 一、定义与核心原理 维度RO…...

Vue3父组件访问子组件方法与属性完全指南

在Vue3的组件化开发中&#xff0c;父子组件间的通信是核心功能之一。本文将详细介绍五种父组件访问子组件属性/方法的实现方案&#xff0c;包含最新的<script setup>语法糖实践。&#xff08;综合1579&#xff09; 一、ref defineExpose&#xff08;推荐方案&#xff0…...

AI时代保护自己的隐私

人工智能最重要的就是数据&#xff0c;让我们面对现实&#xff0c;大多数人都不知道他们每天要向人工智能提供多少数据。你输入的每条聊天记录&#xff0c;你发出的每条语音命令&#xff0c;人工智能生成的每张图片、电子邮件和文本。我建设了一个网站(haptool.com)&#xff0c…...

Android APK组成编译打包流程详解

Android APK&#xff08;Android Package&#xff09;是 Android 应用的安装包文件&#xff0c;其组成和打包流程涉及多个步骤和文件结构。以下是详细的说明&#xff1a; 一、APK 的组成 APK 是一个 ZIP 格式的压缩包&#xff0c;包含应用运行所需的所有文件。解压后主要包含以…...

TCP长连接与短连接

TCP长连接与短连接 TCP&#xff08;传输控制协议&#xff09;中的长连接和短连接是两种不同的连接管理方式&#xff0c;各有优缺点&#xff1a; 短连接 短连接是指客户端与服务器完成一次数据交换后就断开连接。下次需要通信时&#xff0c;再重新建立连接。 特点&#xff1…...

C#委托(delegate)的常用方式

C# 中委托的常用方式&#xff0c;包括委托的定义、实例化、不同的赋值方式以及匿名委托的使用。 委托的定义 // 委托的核心是跟委托的函数结构一样 public delegate string SayHello(string c);public delegate string SayHello(string c);&#xff1a;定义了一个公共委托类型 …...

C#从入门到精通(35)—如何防止winform程序因为误操作被关闭

前言: 大家好,我是上位机马工,硕士毕业4年年入40万,目前在一家自动化公司担任软件经理,从事C#上位机软件开发8年以上!我们在开发的上位机软件运行起来以后,一般在右上角都有一个关闭按钮,正常情况下点击关闭按钮就能关闭软件,但是不排除我们不想关闭软件,但是因为不…...

docker本地镜像源搭建

最近Deepseek大火后&#xff0c;接到任务就是帮客户装Dify&#xff0c;每次都头大&#xff0c;因为docker源不能用&#xff0c;实在没办法&#xff0c;只好自己搭要给本地源。话不多说具体如下&#xff1a; 1、更改docker的配置文件&#xff0c;添加自己的私库地址&#xff0c…...

Sqlserver安全篇之_TLS的证书概念

证书的理解 参考Sqlserver的官方文档https://learn.microsoft.com/zh-cn/sql/database-engine/configure-windows/certificate-overview?viewsql-server-ver16 TLS(Transport Layer Security)传输层安全和SSL(Secure Sockets Layer)安全套接字层协议位于应用程序协议层和TCP/…...

Kafka生产者相关

windows中kafka集群部署示例-CSDN博客 先启动集群或者单机也OK 引入依赖 <dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-clients</artifactId><version>3.9.0</version></dependency>关于主题创建 理论…...

技术问题汇总:前端怎么往后端传一个数组?

场景 现在一个专门负责复习算法的服务&#xff0c;筛选出了用户今天需要复习的笔记的ids&#xff0c;现在要调用笔记服务根据ids查询的接口。 请问复习服务怎么将ids发到笔记服务&#xff0c;笔记服务怎么接收。 思路 发的时候肯定是用字符串&#xff0c;接收的时候&#xf…...

【03】STM32F407 HAL 库框架设计学习

【03】STM32F407 HAL 库框架设计学习 摘要 本文旨在为初学者提供一个关于STM32F407微控制器HAL&#xff08;Hardware Abstraction Layer&#xff09;库框架设计的详细学习教程。通过本文&#xff0c;读者将从零开始&#xff0c;逐步掌握STM32F407的基本知识、HAL库的配置步骤…...

戴森球计划工厂蓝图架构深度解析:构建高效星际生产线的核心策略

戴森球计划工厂蓝图架构深度解析&#xff1a;构建高效星际生产线的核心策略 【免费下载链接】FactoryBluePrints 游戏戴森球计划的**工厂**蓝图仓库 项目地址: https://gitcode.com/GitHub_Trending/fa/FactoryBluePrints FactoryBluePrints 项目作为戴森球计划游戏中最…...

MoE稀疏激活原理与实战:解密大模型高效计算的核心机制

1. 这不是“参数越多越强”的简单故事&#xff1a;拆解大模型里那个被悄悄藏起来的“开关”你肯定见过这类标题&#xff1a;“GPT-4 参数量突破1.8万亿&#xff01;”、“DeepSeek-R1 达到6710亿参数&#xff01;”——光看数字&#xff0c;像在比谁家粮仓堆得更高。但真正懂行…...

Win11Debloat:Windows系统优化利器,一键清理臃肿应用与隐私设置

Win11Debloat&#xff1a;Windows系统优化利器&#xff0c;一键清理臃肿应用与隐私设置 【免费下载链接】Win11Debloat A simple, lightweight PowerShell script that allows you to remove pre-installed apps, disable telemetry, as well as perform various other changes…...

技术革新:FModel如何重塑游戏资源逆向工程与创作流程

技术革新&#xff1a;FModel如何重塑游戏资源逆向工程与创作流程 【免费下载链接】FModel Unreal Engine Archives Explorer 项目地址: https://gitcode.com/gh_mirrors/fm/FModel 在游戏开发与内容创作的生态系统中&#xff0c;资源逆向工程长期以来都是一项技术壁垒高…...

2026山东省数学建模D题思路分享(附源码,论文)

D题&#xff1a;基于多维指标因子分析的"苏超"球队竞争力评估 —— 解题思路 完整思路&#xff0c;代码&#xff0c;论文:https://docs.qq.com/doc/DUVJPa1R5YWhHaE9Y 一、问题概述 江苏省城市足球联赛&#xff08;“苏超”&#xff09;以"一城一主场"模式…...

终极指南:5步掌握.NET Core Mod加载器Reloaded-II的完整使用方法

终极指南&#xff1a;5步掌握.NET Core Mod加载器Reloaded-II的完整使用方法 【免费下载链接】Reloaded-II Universal .NET Core Powered Modding Framework for any Native Game X86, X64. 项目地址: https://gitcode.com/gh_mirrors/re/Reloaded-II 你是否厌倦了手动复…...

COMET:基于深度学习的翻译质量评估技术革命

COMET&#xff1a;基于深度学习的翻译质量评估技术革命 【免费下载链接】COMET A Neural Framework for MT Evaluation 项目地址: https://gitcode.com/gh_mirrors/com/COMET 在机器翻译技术快速发展的今天&#xff0c;翻译质量评估已成为连接技术研发与实际应用的关键…...

限流算法详解 - 滑动窗口算法深入理解

滑动窗口算法详解 专门针对滑动窗口算法&#xff0c;从原理到精确限流的实现细节做一个深入剖析。 一、核心思想 固定窗口在时间边界处会出现“计数突跳”&#xff0c;原因是窗口的边界是硬重置的&#xff08;0→1秒末清空&#xff0c;1→2秒初重新计数&#xff09;。 滑动窗口…...

别再死记硬背了!用STM32CubeMX配置GPIO模式,这3个坑我帮你踩过了

STM32CubeMX实战&#xff1a;GPIO配置避坑指南与典型场景解析 刚接触STM32开发的工程师们&#xff0c;面对CubeMX中眼花缭乱的GPIO配置选项时&#xff0c;是否常感到困惑&#xff1f;推挽与开漏输出如何选择&#xff1f;上拉和下拉电阻何时需要&#xff1f;本文将结合LED驱动、…...

JWT安全实战:从算法漏洞到生产级防御体系

1. 为什么JWT不是“自带安全”的令牌&#xff0c;而是一把双刃剑JWT&#xff08;JSON Web Token&#xff09;在现代Web应用中几乎无处不在——登录成功后返回一串Base64Url编码的字符串&#xff0c;前端存进localStorage&#xff0c;后续请求带上Bearer头&#xff0c;后端解析、…...