工程化與框架系列(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. 保持技術棧的適度統一

如果你覺得這篇文章有幫助,歡迎點贊收藏,也期待在評論區看到你的想法和建議!👇

終身學習,共同成長。

咱們下一期見

💻

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/71160.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/71160.shtml
英文地址,請注明出處:http://en.pswp.cn/web/71160.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SwiftUI之狀態管理全解析

文章目錄 引言一、`@State`1.1 基本概念1.2 初始化與默認值1.3 注意事項二、`@Binding`2.1 基本概念2.2 初始化與使用2.3 注意事項三、`@ObservedObject`3.1 基本概念3.2 初始化與使用3.3 注意事項四、`@EnvironmentObject`4.1 基本概念4.2 初始化與使用4.3 注意事項五、`@Stat…

Redis 高可用性:如何讓你的緩存一直在線,穩定運行?

&#x1f3af; 引言&#xff1a;Redis的高可用性為啥這么重要&#xff1f; 在現代高可用系統中&#xff0c;Redis 是一款不可或缺的分布式緩存與數據庫系統。無論是提升訪問速度&#xff0c;還是實現數據的高效持久化&#xff0c;Redis 都能輕松搞定。可是&#xff0c;當你把 …

面試題:說一下你對DDD的了解?

面試題:說一下你對DDD的了解? 在面試中,關于 DDD(領域驅動設計,Domain-Driven Design) 的問題是一個常見的技術考察點。DDD 是一種軟件設計方法論,旨在通過深入理解業務領域來構建復雜的軟件系統。以下是一個清晰、詳細的回答模板,幫助你在面試中脫穎而出: DDD 的定義…

Redis---緩存穿透,雪崩,擊穿

文章目錄 緩存穿透什么是緩存穿透&#xff1f;緩存穿透情況的處理流程是怎樣的&#xff1f;緩存穿透的解決辦法緩存無效 key布隆過濾器 緩存雪崩什么是緩存雪崩&#xff1f;緩存雪崩的解決辦法 緩存擊穿什么是緩存擊穿&#xff1f;緩存擊穿的解決辦法 區別對比 在如今的開發中&…

Android Logcat 高效調試指南

工具概覽 Logcat 是 Android SDK 提供的命令行日志工具&#xff0c;支持靈活過濾、格式定制和實時監控&#xff0c;官方文檔詳見 Android Developer。 基礎用法 命令格式 [adb] logcat [<option>] ... [<filter-spec>] ... 執行方式 直接調用&#xff08;通過ADB守…

【定昌Linux系統】部署了java程序,設置開啟啟動

將代碼上傳到相應的目錄&#xff0c;并且配置了一個.sh的啟動腳本文件 文件內容&#xff1a; #!/bin/bash# 指定JAR文件的路徑&#xff08;如果JAR文件在當前目錄&#xff0c;可以直接使用文件名&#xff09; JAR_FILE"/usr/local/java/xs_luruan_client/lib/xs_luruan_…

Java 8 中,可以使用 Stream API 和 Comparator 對 List 按照元素對象的時間字段進行倒序排序

文章目錄 引言I 示例對象II List 按時間字段倒序排序: 使用 `Stream` 和 `Comparator` 排序方法 1:使用 `Comparator.comparing`方法 2:使用 `Comparator.reversed`方法 3:自定義 `Comparator`輸出結果III 注意事項**時間字段類型**:**空值處理**:IV 總結引言 案例:在線用…

jvm內存模型,類加載機制,GC算法,垃圾回收器,jvm線上調優等常見的面試題及答案

JVM內存模型 JVM內存模型包括哪些區域 答案&#xff1a;JVM內存模型主要包括以下區域&#xff1a; 程序計數器&#xff1a;是一塊較小的內存空間&#xff0c;它可以看作是當前線程所執行的字節碼的行號指示器&#xff0c;用于記錄正在執行的虛擬機字節碼指令的地址。Java虛擬機…

git clone的時候出現出現error

報錯如下&#xff1a; Collecting githttps://github.com/haotian-liu/LLaVA.git Cloning https://github.com/haotian-liu/LLaVA.git to /tmp/pip-req-build-360q6tt1 Running command git clone --filterblob:none --quiet https://github.com/haotian-liu/LLaVA.git /t…

Minio搭建并在SpringBoot中使用完成用戶頭像的上傳

Minio使用搭建并上傳用戶頭像到服務器操作,學習筆記 Minio介紹 minio官網 MinIO是一個開源的分布式對象存儲服務器&#xff0c;支持S3協議并且可以在多節點上實現數據的高可用和容錯。它采用Go語言開發&#xff0c;擁有輕量級、高性能、易部署等特點&#xff0c;并且可以自由…

vue3中ref和reactive響應式數據、ref模板引用(組合式和選項式區別)、組件ref的使用

目錄 Ⅰ.ref 1.基本用法&#xff1a;ref響應式數據 2.ref模板引用 3.ref在v-for中的模板引用 ?4.ref在組件上使用 ?5.TS中ref數據標注類型 Ⅱ.reactive 1.基本用法&#xff1a;reactive響應式數據 2.TS中reactive標注類型 Ⅲ.ref和reactive的使用場景和區別 Ⅳ.小結…

javascript實現雪花飄落效果

本文實現雪花飄落效果的 JavaScript 網頁設計案例&#xff0c;代碼實現如下&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, init…

項目準備(flask+pyhon+MachineLearning)- 3

目錄 1.商品信息 2. 商品銷售預測 2.1 機器學習 2.2 預測功能 3. 模型評估 1.商品信息 app.route(/products) def products():"""商品分析頁面"""data load_data()# 計算當前期間和上期間current_period data[data[成交時間] > data[成…

FPGA開發,使用Deepseek V3還是R1(3):系統級與RTL級

以下都是Deepseek生成的答案 FPGA開發&#xff0c;使用Deepseek V3還是R1&#xff08;1&#xff09;&#xff1a;應用場景 FPGA開發&#xff0c;使用Deepseek V3還是R1&#xff08;2&#xff09;&#xff1a;V3和R1的區別 FPGA開發&#xff0c;使用Deepseek V3還是R1&#x…

實現 Leaflet 多類型點位標記與聚合功能的實戰經驗分享

在現代的地理信息系統&#xff08;GIS&#xff09;應用中&#xff0c;地圖功能是不可或缺的一部分。無論是展示商業網點、旅游景點還是公共服務設施&#xff0c;地圖都能以直觀的方式呈現數據。然而&#xff0c;當數據量較大時&#xff0c;地圖上可能會出現大量的標記點&#x…

企微審批中MySQL字段TEXT類型被截斷的排查與修復實踐

在MySQL中&#xff0c;TEXT類型字段常用于存儲較大的文本數據&#xff0c;但在一些應用場景中&#xff0c;當文本內容較大時&#xff0c;TEXT類型字段可能無法滿足需求&#xff0c;導致數據截斷或插入失敗。為了避免這種問題&#xff0c;了解不同文本類型&#xff08;如TEXT、M…

【常見BUG】Spring Boot 和 Springfox(Swagger)版本兼容問題

???歡迎來到我的博客&#xff0c;很高興能夠在這里和您見面&#xff01;希望您在這里可以感受到一份輕松愉快的氛圍&#xff0c;不僅可以獲得有趣的內容和知識&#xff0c;也可以暢所欲言、分享您的想法和見解。 推薦:kwan 的首頁,持續學習,不斷總結,共同進步,活到老學到老…

HTTP 協議的發展歷程:從 HTTP/1.0 到 HTTP/2.0

HTTP 協議的發展歷程&#xff1a;從 HTTP/1.0 到 HTTP/2.0 HTTP&#xff08;HyperText Transfer Protocol&#xff0c;超文本傳輸協議&#xff09;是 Web 的基礎協議&#xff0c;用于客戶端和服務器之間的通信。從 HTTP/1.0 到 HTTP/2.0&#xff0c;HTTP 協議經歷了多次重大改…

apload-lab打靶場

1.提示顯示所以關閉js 上傳<?php phpinfo(); ?>的png形式 抓包&#xff0c;將png改為php 然后放包上傳成功 2.提示說檢查數據類型 抓包 將數據類型改成 image/jpeg 上傳成功 3.提示 可以用phtml&#xff0c;php5&#xff0c;php3 4.先上傳.htaccess文件&#xff0…

金融支付行業技術側重點

1. 合規問題 第三方支付系統的平穩運營&#xff0c;嚴格遵循《非銀行支付機構監督管理條例》的各項條款是基礎與前提&#xff0c;其中第十八條的規定堪稱重中之重&#xff0c;是支付機構必須牢牢把握的關鍵準則。 第十八條明確指出&#xff0c;非銀行支付機構需構建起必要且獨…