当前位置: 首页 > 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库的配置步骤…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

【第二十一章 SDIO接口(SDIO)】

第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要&#xff1a; 近期&#xff0c;在使用较新版本的OpenSSH客户端连接老旧SSH服务器时&#xff0c;会遇到 "no matching key exchange method found"​, "n…...

数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !

我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...

GAN模式奔溃的探讨论文综述(一)

简介 简介:今天带来一篇关于GAN的,对于模式奔溃的一个探讨的一个问题,帮助大家更好的解决训练中遇到的一个难题。 论文题目:An in-depth review and analysis of mode collapse in GAN 期刊:Machine Learning 链接:...

五、jmeter脚本参数化

目录 1、脚本参数化 1.1 用户定义的变量 1.1.1 添加及引用方式 1.1.2 测试得出用户定义变量的特点 1.2 用户参数 1.2.1 概念 1.2.2 位置不同效果不同 1.2.3、用户参数的勾选框 - 每次迭代更新一次 总结用户定义的变量、用户参数 1.3 csv数据文件参数化 1、脚本参数化 …...