工程化与框架系列(10)--微前端架构
微前端架构 🏗️
微前端是一种将前端应用分解成更小、更易管理的独立部分的架构模式。本文将详细介绍微前端的核心概念、实现方案和最佳实践。
微前端概述 🌟
💡 小知识:微前端的核心理念是将前端应用分解成一系列独立部署、松耦合的小型应用,每个应用可以由不同的团队使用不同的技术栈开发。
为什么需要微前端
在大型前端应用开发中,微前端架构能带来以下优势:
-
技术栈灵活性
- 支持多框架共存
- 渐进式技术迁移
- 团队技术选择自由
- 复用已有应用资产
-
团队自主性
- 独立开发部署
- 团队边界清晰
- 降低协作成本
- 提高开发效率
-
应用可维护性
- 代码库规模可控
- 模块职责单一
- 降低耦合度
- 简化测试和部署
-
性能优化空间
- 按需加载应用
- 独立缓存策略
- 资源并行加载
- 性能瓶颈隔离
实现方案详解 ⚡
基于路由的实现
// 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> {// 应用卸载}
}
结语 📝
微前端架构为大型前端应用开发提供了一种可扩展、可维护的解决方案。通过本文,我们学习了:
- 微前端的核心概念和价值
- 不同的实现方案及其特点
- 应用间通信机制的实现
- 样式隔离和资源加载优化
- 微前端的最佳实践和建议
💡 学习建议:
- 从小规模试点开始,逐步扩大应用范围
- 注重基础设施和工具链建设
- 建立完善的开发规范和文档
- 重视性能优化和用户体验
- 保持技术栈的适度统一
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻
相关文章:
工程化与框架系列(10)--微前端架构
微前端架构 🏗️ 微前端是一种将前端应用分解成更小、更易管理的独立部分的架构模式。本文将详细介绍微前端的核心概念、实现方案和最佳实践。 微前端概述 🌟 💡 小知识:微前端的核心理念是将前端应用分解成一系列独立部署、松耦…...
【3天快速入门WPF】11-附加属性
目录 1. 步骤1:定义附加属性2. 示例代码3. 步骤2:在XAML中使用附加属性3.1. 示例代码4. 步骤3:扩展使用场景4.1. 示例代码5. 总结上一篇讲到了依赖属性,本篇主要想说一下附加属性。 在WPF中,附加属性(Attached Property)是一种特殊的依赖属性,允许你在不属于某个类的控…...
MySQL并发知识(面试高频)
mysql并发事务解决 不同隔离级别下,mysql解决并发事务的方式不同。主要由锁机制和MVCC(多版本并发控制)机制来解决并发事务问题。 1. mysql中的锁有哪些? 表级锁: 场景:表级锁适用于需要对整个表进行操作的情况,例如…...
现存脑容知识库
Redis import queue import threading import asyncio 异步:在一个线程内,等待的时候可以切换到其他任务。 多线程:每个线程独立运行,同时处理多个任务。 回调函数 网络请求(JavaScript)在浏览器中&a…...
Mysql-如何理解事务?
一、事务是什么东西 有些场景中,某个操作需要多个sql配合完成: 例如: 李四这个月剩下的前不够交房租了,找张三借1000元急用: (1)给张三的账户余额 减去1000元 updata 账户表 set money money -…...
dify绑定飞书多维表格
dify 绑定飞书和绑定 notion 有差不多的过程,都需要套一层应用的壳子,而没有直接可以访问飞书文档的 API。本文记录如何在dify工具中使用新增多条记录工具。 创建飞书应用 在飞书开放平台创建一个应用,个人用户创建企业自建应用。 自定义应…...
QT播放视频保持视频宽高比消除黑边
QT播放视频保持视频宽高比消除黑边 1、问题 在播放视频的时候,由于框架的大小发生变化,导致视频出现黑边很不好看。 因此需要像一种方法消除黑边 2、处理 1、读取视频的宽高比 2、设置视频的Widget的大小固定,Widget的宽高比和视频宽高比…...
1. IO的基础知识
1.1 流 Java程序通过流执行IO。流是一种抽象,它要么生成信息,要么使用信息。流通过java的IO系统链接到物理设备。所有流的行为方式都是相同的,尽管它们链接的物理设备是不同的。 1.2 字节流和字符流 Java定义了两种类型的流 : 字节流和字符流…...
科普:ROC AUC与PR AUC
在评价二分类模型性能时,有许多评价指标,其中,有一对是用面积AUC(Area Under the Curve)做评价的:ROC AUC与PR AUC 本文我们对ROC AUC与PR AUC进行多维度对比分析: 一、定义与核心原理 维度RO…...
Vue3父组件访问子组件方法与属性完全指南
在Vue3的组件化开发中,父子组件间的通信是核心功能之一。本文将详细介绍五种父组件访问子组件属性/方法的实现方案,包含最新的<script setup>语法糖实践。(综合1579) 一、ref defineExpose(推荐方案࿰…...
AI时代保护自己的隐私
人工智能最重要的就是数据,让我们面对现实,大多数人都不知道他们每天要向人工智能提供多少数据。你输入的每条聊天记录,你发出的每条语音命令,人工智能生成的每张图片、电子邮件和文本。我建设了一个网站(haptool.com),…...
Android APK组成编译打包流程详解
Android APK(Android Package)是 Android 应用的安装包文件,其组成和打包流程涉及多个步骤和文件结构。以下是详细的说明: 一、APK 的组成 APK 是一个 ZIP 格式的压缩包,包含应用运行所需的所有文件。解压后主要包含以…...
TCP长连接与短连接
TCP长连接与短连接 TCP(传输控制协议)中的长连接和短连接是两种不同的连接管理方式,各有优缺点: 短连接 短连接是指客户端与服务器完成一次数据交换后就断开连接。下次需要通信时,再重新建立连接。 特点࿱…...
C#委托(delegate)的常用方式
C# 中委托的常用方式,包括委托的定义、实例化、不同的赋值方式以及匿名委托的使用。 委托的定义 // 委托的核心是跟委托的函数结构一样 public delegate string SayHello(string c);public delegate string SayHello(string c);:定义了一个公共委托类型 …...
C#从入门到精通(35)—如何防止winform程序因为误操作被关闭
前言: 大家好,我是上位机马工,硕士毕业4年年入40万,目前在一家自动化公司担任软件经理,从事C#上位机软件开发8年以上!我们在开发的上位机软件运行起来以后,一般在右上角都有一个关闭按钮,正常情况下点击关闭按钮就能关闭软件,但是不排除我们不想关闭软件,但是因为不…...
docker本地镜像源搭建
最近Deepseek大火后,接到任务就是帮客户装Dify,每次都头大,因为docker源不能用,实在没办法,只好自己搭要给本地源。话不多说具体如下: 1、更改docker的配置文件,添加自己的私库地址,…...
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>关于主题创建 理论…...
技术问题汇总:前端怎么往后端传一个数组?
场景 现在一个专门负责复习算法的服务,筛选出了用户今天需要复习的笔记的ids,现在要调用笔记服务根据ids查询的接口。 请问复习服务怎么将ids发到笔记服务,笔记服务怎么接收。 思路 发的时候肯定是用字符串,接收的时候…...
【03】STM32F407 HAL 库框架设计学习
【03】STM32F407 HAL 库框架设计学习 摘要 本文旨在为初学者提供一个关于STM32F407微控制器HAL(Hardware Abstraction Layer)库框架设计的详细学习教程。通过本文,读者将从零开始,逐步掌握STM32F407的基本知识、HAL库的配置步骤…...
基于算法竞赛的c++编程(28)结构体的进阶应用
结构体的嵌套与复杂数据组织 在C中,结构体可以嵌套使用,形成更复杂的数据结构。例如,可以通过嵌套结构体描述多层级数据关系: struct Address {string city;string street;int zipCode; };struct Employee {string name;int id;…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
vue3 字体颜色设置的多种方式
在Vue 3中设置字体颜色可以通过多种方式实现,这取决于你是想在组件内部直接设置,还是在CSS/SCSS/LESS等样式文件中定义。以下是几种常见的方法: 1. 内联样式 你可以直接在模板中使用style绑定来设置字体颜色。 <template><div :s…...
视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
Spring Security 认证流程——补充
一、认证流程概述 Spring Security 的认证流程基于 过滤器链(Filter Chain),核心组件包括 UsernamePasswordAuthenticationFilter、AuthenticationManager、UserDetailsService 等。整个流程可分为以下步骤: 用户提交登录请求拦…...
[论文阅读]TrustRAG: Enhancing Robustness and Trustworthiness in RAG
TrustRAG: Enhancing Robustness and Trustworthiness in RAG [2501.00879] TrustRAG: Enhancing Robustness and Trustworthiness in Retrieval-Augmented Generation 代码:HuichiZhou/TrustRAG: Code for "TrustRAG: Enhancing Robustness and Trustworthin…...
