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

微前端架构详解

微前端架构详解

一、什么是微前端

微前端是微服务的概念在前端领域的扩展。将Web应用拆分成多个子应用,每个应用由独立的团队开发、测试、部署。

请添加图片描述

微前端的原则:

  • 技术栈无关:可根据业务领域灵活混用Vue\React\Angular等技术栈
  • 代码隔离,无法隔离的代码,应用之间采用前缀来区分
  • 独立交付:独立开发、独立测试、独立部署
  • 团队自治:团队负责全生命周期管理,减少协作成本

典型应用场景

  • 遗留系统现代化改造
  • 多团队协作的大型 SaaS 平台
  • 需要动态加载不同功能模块的 Portal 类应用

二、微前端要解决的问题

1、通信机制

  • 子应用之间通信:共享状态库(Redux、Vuex、Pina等)、URL参数等
  • 父子应用通信: 使用CustomEvent发布订阅模式

2、应用隔离

隔离类型问题描述解决方案
JS沙箱全局变量/事件监听污染Proxy代理、Shadow Realm、快照沙箱
CSS隔离样式冲突、选择器冲突CSS Scope、命名空间、Shadow Dom
存储隔离Cookie/LocalStorage冲突前缀命名、封装API

3、资源加载与共享

  • 问题:公共依赖如何加载,避免公共库被多次加载
  • 方案:
    • Webpack 的 externals 将公共库排除。

4、路由与导航

  • 问题:主应用于子应用路由冲突,浏览器历史记录管理
  • 方案:
    • 主应用统一路由分发(URL前缀匹配)
    • 使用history.pushState实现无刷新跳转
    • 路由劫持

三、微前端的实现原理

1、通过路由映射分发

路由匹配 /app1/*
路由匹配 /app2/*
主应用
子应用1
子应用2

2、iframe嵌套

  • 原理:每个子应用独立运行在 iframe 中。
  • 优点:天然隔离(JS/CSS/DOM)。
  • 缺点:URL 不同步、性能差、通信复杂(postMessage)。

3、WebComponent

webComponent有三个步骤:

  • 使用customElements定义组件
  • 使用template包含组件的HTML内容,支持使用slot插槽
  • 使用Shadow DOM将元素的样式和行为封装在一个隔离的DOM中
<!-- 主应用调用 -->
<micro-app name="cart" src="https://child.com/cart.js"></micro-app><!-- 子应用封装 -->
<template id="tpl"><style>/* Scoped CSS */</style><div>子应用内容</div>
</template>
<script>class MicroApp extends HTMLElement {connectedCallback() {this.attachShadow({ mode: 'open' }).append(tpl.content.cloneNode(true));}}customElements.define('micro-app', MicroApp);
</script>

3、Module Federation

Module Federation是Webpack5提出来的概念,用来解决多个应用之间代码共享的问题。

核心概念

  • Host(主应用):消费远程模块的容器
  • Remote(子应用):暴露模块的远程服务
  • Shared:共享依赖(如 React、Vue)
  • **Container:**被MF构建的模块
/// 配置webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;
new ModuleFederationPlugin({name: "appA",//出口文件filename: "remoteEntry.js",//暴露可访问的组件exposes: {"./input": "./src/input",},//或者其他模块的组件//如果把这一模块当作基座模块的话,//这里应该配置其他子应用模块的入口文件remotes: {appB: "appB@http://localhost:3002/remoteEntry.js",},//共享依赖,其他模块不需要再次下载,便可使用shared: ['react', 'react-dom'],
})

四、微前端的框架

4.1 single-spa

  • 原理:提供加载器、路由托管,不处理资源加载/隔离。

  • 工作流程

    1. 子应用导出 bootstrap, mount, unmount 函数。
    2. 主应用注册子应用并配置路由匹配规则。
    3. 路由变化时,加载并执行对应子应用。
  • 示例配置

    singleSpa.registerApplication('app1',() => System.import('app1'), // 动态加载location => location.pathname.startsWith('/app1')
    );
    

4.2 qiankun(基于 single-spa)

  • 核心增强
    • HTML Entry:解析子应用 HTML 获取资源列表,替代 JS Entry。
    • JS 沙箱:支持 Proxy 和快照沙箱(兼容 IE)。
    • 样式隔离:自动为子应用 CSS 添加前缀隔离。
    • 预加载:空闲时预加载子应用资源。
主应用
路由分发
匹配子应用
qiankun-loader
HTML Entry解析
JS沙箱环境
执行子应用
挂载到容器
import { registerMicroApps, start } from 'qiankun';registerMicroApps([{name: 'reactApp',entry: '//localhost:3000',container: '#container',activeRule: '/app-react',},{name: 'vueApp',entry: '//localhost:8080',container: '#container',activeRule: '/app-vue',},{name: 'angularApp',entry: '//localhost:4200',container: '#container',activeRule: '/app-angular',},
]);
// 启动 qiankun
start();

需要注意的是,如果采用vite构建的应用,需要使用插件vite-plugin-qiankun

// 子应用vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import qiankun from 'vite-plugin-qiankun'export default defineConfig({plugins: [vue(),qiankun('vue-app', { // 子应用名字,与主应用注册的子应用名字保持一致useDevMode: true})],server: {origin: 'http://localhost:5174', // 解决静态资源加载404问题host: 'localhost',port: 5174}
})//子应用main.js
import { createApp } from 'vue'
import App from './App.vue'
import { store } from './store'import { renderWithQiankun, qiankunWindow } from 'vite-plugin-qiankun/dist/helper'let instance = null;
const initQianKun = () => {renderWithQiankun({mount(props) {render(props.container)//  可以通过props读取主应用的参数:msg// 监听主应用传值props.onGlobalStateChange((res) => {store.count = res.countconsole.log(res.count)})},bootstrap() { },unmount() {instance.unmount()instance._instance = nullinstance = null},update() { }})
}const render = (container) => {if (instance) return;// 如果是在主应用的环境下就挂载主应用的节点,否则挂载到本地// 注意:这边需要避免 id(app) 重复导致子应用挂载失败const appDom = container ? container.querySelector("#app") : "#app"instance = createApp(App)instance.mount(appDom)
}// 判断当前应用是否在主应用中
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render()// route文件
import { createWebHashHistory } from 'vue-router'
import { qiankunWindow } from 'vite-plugin-qiankun/dist/helper'
const router = createRouter({// 值和主应用中的activeRule保持一致    history: createWebHashHistory(qiankunWindow.__POWERED_BY_QIANKUN__ ? '/vueApp' : '/'),     routes: routes 
});

详细实践: 点击这里查看实践指南

4.3 Mirco-Ap

Mirco-App是京东出的一款基于 Web Component 原生组件进行渲染的微前端框架,不同于目前流行的开源框架,它从组件化的思维实现微前端,旨在降低上手难度、提升工作效率。

主应用
micro-app
自定义元素
创建Shadow DOM
资源加载器
JS作用域隔离
样式隔离
子应用渲染

核心技术实现

  1. Web Components集成

    • 自定义元素注册
    class MicroApp extends HTMLElement {constructor() {super();this.attachShadow({ mode: 'open' });}connectedCallback() {this.loadApp();}
    }customElements.define('micro-app', MicroApp);
    
  2. JS作用域隔离

    • 基于Proxy的沙箱
    function createSandbox(appName) {const proxy = new Proxy(window, {get(target, key) {if (key in target) {return target[key];}// 返回子应用自有属性},set(target, key, value) {if (!target.hasOwnProperty(key)) {// 存储到子应用独立空间target[`${appName}_${key}`] = value;} else {target[key] = value;}return true;}});return proxy;
    }
    
  3. 样式隔离策略

    • Shadow DOM原生隔离
    • 自动前缀转换
    /* 输入 */
    .container { background: #fff; }/* 输出 */
    micro-app[name=app1] .container { background: #fff; }
    
  4. 数据通信机制

    • 基于CustomEvent
    // 主应用发送数据
    const event = new CustomEvent('micro-app-event', {detail: { type: 'data-update', payload }
    });
    document.querySelector('micro-app').dispatchEvent(event);// 子应用接收
    window.addEventListener('micro-app-event', (e) => {console.log(e.detail);
    });
    

4.4 无界

无界是腾讯推出的一款微前端解决方式。它是一种基于 Web Components + iframe 的全新微前端方案。

点击这里查看方案设计

核心技术:

  1. 无界沙箱

    • 基于iframe的天然隔离
    • 主应用创建代理iframe:<iframe src="about:blank"></iframe>
    • 子应用运行在隐藏iframe中
  2. DOM代理机制

    • 劫持iframe内的document
    const proxyDocument = new Proxy(document, {get: function(target, key) {if (key === 'createElement') {return function(tagName) {const element = target.createElement(tagName);// 代理元素到主应用return elementProxy(element);};}return target[key];}
    });
    
    • 同步DOM到主应用容器
  3. 事件代理

    • 劫持事件监听方法
    Element.prototype.addEventListener = function(type, listener, options) {// 存储事件监听器this._listeners = this._listeners || {};this._listeners[type] = this._listeners[type] || [];this._listeners[type].push(listener);// 在主应用容器上绑定代理事件hostElement.addEventListener(type, proxyListener);
    };
    
  4. 路由同步

    • 监听iframe的history变化
    • 同步到主应用URL
    iframe.contentWindow.history.pushState = new Proxy(history.pushState, {apply: function(target, thisArg, args) {const result = target.apply(thisArg, args);// 同步主应用URLwindow.history.pushState(...args);return result;}
    });
    
主应用
wujie实例
创建iframe
iframe沙箱
DOM代理
事件代理
子应用运行

五、原理解析

1、JS沙箱机制实现

  • 快照沙箱: 通过将window的属性复制到windowSnapshot
class SnapshotSandbox {constructor() {this.proxy = window;this.modifyPropsMap = {};this.active();}active() {this.windowSnapshot = {};for (const prop in window) {this.windowSnapshot[prop] = window[prop];}Object.keys(this.modifyPropsMap).forEach(p => {window[p] = this.modifyPropsMap[p];});}inactive() {for (const prop in window) {if (window[prop] !== this.windowSnapshot[prop]) {this.modifyPropsMap[prop] = window[prop];window[prop] = this.windowSnapshot[prop];}}}
}
  • 代理沙箱: 使用proxy实现
class ProxySandbox {constructor() {const rawWindow = window;const fakeWindow = {};this.proxy = new Proxy(fakeWindow, {set(target, key, value) {target[key] = value;return true;},get(target, key) {return target[key] || rawWindow[key];}});}
}

2、Shadow Realm

Shadow Realm 是 ECMAScript 2023 正式标准,提供真正的 JavaScript 运行时隔离环境。与传统沙箱不同,它创建完全独立的全局对象和内置函数副本,实现真正的环境隔离。

class ShadowRealm {constructor() {// 创建全新的全局对象this.global = new Proxy({}, {get: (_, key) => this._scopedGlobals[key]});// 复制内置函数this._scopedGlobals = {Array: function() { /* 独立实现 */ },Date: function() { /* 独立实现 */ },// ...其他内置对象};}evaluate(sourceText) {// 在隔离环境中执行代码const wrapped = `(function(__global__) {with(__global__) { ${sourceText} }})`;return (0, eval)(wrapped)(this.global);}
}// 创建子应用沙箱
const appRealm = new ShadowRealm();// 执行子应用代码
appRealm.evaluate(`class MyApp {mount(container) {this.el = document.createElement('div');this.el.textContent = '隔离的子应用';container.appendChild(this.el);}}new MyApp();
`);// 获取子应用导出
const { MyApp } = await appRealm.importValue('./app.js', 'MyApp');
const app = new MyApp();
app.mount(document.getElementById('container'));

3、CSS添加命名空间

function scopeCSS(css, prefix) {return css.replace(/([^{}]+)(?=\s*{)/g, selector => {return selector.split(',').map(part => {return `${prefix} ${part.trim()}`}).join(',');});
}// 输入: .btn, .header { color: red; }
// 输出: [data-app="app1"] .btn, [data-app="app1"] .header { color: red; }

4、 Scoped CSS

通过属性选择器实现作用域隔离

<style>/* 自动转换 */.container[data-v-7ba5bd90] { color: red; }
</style><div class="container" data-v-7ba5bd90>内容</div>

5、Shadow DOM

架构原理

宿主元素
Shadow Root
Shadow Tree
样式隔离
DOM 封装
元素隐藏

核心特性实现

  1. 创建 Shadow DOM

    const host = document.getElementById('host');
    const shadowRoot = host.attachShadow({ mode: 'open' });
    
  2. 样式隔离

    <style>:host { /* 宿主元素样式 */ }.btn { /* 仅内部有效 */ }
    </style>
    
  3. 插槽机制(Slot)

    <!-- 定义 -->
    <div class="card"><slot name="title">默认标题</slot><slot name="content"></slot>
    </div><!-- 使用 -->
    <my-card><h1 slot="title">自定义标题</h1><p slot="content">自定义内容</p>
    </my-card>
    
  4. 事件重定向

    shadowRoot.addEventListener('click', e => {// 事件目标重定向e.target = host;host.dispatchEvent(new CustomEvent('shadow-click', e));
    });
    

微前端应用示例

class MicroApp extends HTMLElement {constructor() {super();this.attachShadow({ mode: 'open' });}connectedCallback() {this.render();}render() {this.shadowRoot.innerHTML = `<style>:host { display: block; border: 1px solid #ddd; }h2 { color: var(--primary-color, blue); }</style><div class="app-container"><h2>子应用标题</h2><slot name="content"></slot></div>`;}
}customElements.define('micro-app', MicroApp);

6、 HTML Enity

与传统 JS Entry 对比

特性JS EntryHTML Entry
入口文件app.jsindex.html
资源声明需手动配置 externals自动解析 link/script 标签
样式处理独立加载 CSS 文件自动提取并注入样式
子应用结构需暴露生命周期函数无需改造,直接接入

实现原理

核心代码实现

class HTMLEntryLoader {async loadApp(url, container) {// 1. 获取 HTMLconst html = await fetch(url).then(r => r.text());// 2. 解析 DOMconst parser = new DOMParser();const doc = parser.parseFromString(html, 'text/html');// 3. 提取资源const styles = [...doc.querySelectorAll('link[rel="stylesheet"]')];const scripts = [...doc.querySelectorAll('script')];// 4. 创建沙箱容器const appContainer = document.createElement('div');appContainer.id = `app-${Date.now()}`;container.appendChild(appContainer);// 5. 加载样式styles.forEach(style => {const link = document.createElement('link');link.rel = 'stylesheet';link.href = new URL(style.href, url).href;document.head.appendChild(link);});// 6. 执行脚本const execContext = new ShadowRealm();for (const script of scripts) {if (script.src) {const scriptUrl = new URL(script.src, url).href;const code = await fetch(scriptUrl).then(r => r.text());execContext.evaluate(code);} else {execContext.evaluate(script.textContent);}}// 7. 渲染内容appContainer.innerHTML = doc.body.innerHTML;}
}

高级特性实现

  1. 资源预加载

    function prefetchResources(html) {const parser = new DOMParser();const doc = parser.parseFromString(html, 'text/html');// 预加载所有资源[...doc.querySelectorAll('[src],[href]')].forEach(res => {const url = res.src || res.href;const link = document.createElement('link');link.rel = 'prefetch';link.href = url;document.head.appendChild(link);});
    }
    
  2. 依赖去重

    const sharedLibs = ['react', 'react-dom'];function filterDuplicateScripts(scripts) {return scripts.filter(script => {return !sharedLibs.some(lib =>script.src.includes(`/${lib}/`) && window[lib]);});
    }
    
  3. 样式作用域

    function scopeStyles(styleContent, prefix) {return styleContent.replace(/([^{}]+)(?=\s*{)/g,selector => `${prefix} ${selector}`);
    }
    

六、三大框架对比分析

特性qiankunwujiemicro-app
隔离机制JS沙箱+样式重写iframe沙箱+DOM代理Shadow DOM+Proxy沙箱
通信方式全局状态管理props/$wujie对象CustomEvent+数据监听
路由同步手动配置activeRule自动同步自动同步
性能开销中等(需要解析HTML)较低(iframe原生隔离)低(原生Web Components)
接入成本需子应用改造导出生命周期无侵入无侵入
多实例支持
IE兼容性✓(降级方案)
SSR支持
http://www.xdnf.cn/news/1120465.html

相关文章:

  • 《C++初阶之STL》【泛型编程 + STL简介】
  • Nacos 技术研究文档(基于 Nacos 3)
  • 基于R语言的极值统计学及其在相关领域中的实践技术应用
  • 迅为八核高算力RK3576开发板摄像头实时推理测试 ppyoloe目标检测
  • 《亿级流量系统架构设计与实战》通用高并发架构设计 读场景
  • 文心4.5开源之路:引领技术开放新时代!
  • Go从入门到精通(22) - 一个简单web项目-统一日志输出
  • 如何单独安装设置包域名
  • LeetCode--45.跳跃游戏 II
  • 雷卯针对灵眸科技RV1106G3开发板防雷防静电方案
  • AI数字人正成为医药行业“全场景智能角色”,魔珐科技出席第24届全国医药工业信息年会
  • 2024年中国公交网络数据集(Shp/分城市)
  • 【DOCKER】-6 docker的资源限制与监控
  • 【机器学习深度学习】Ollama vs vLLM vs LMDeploy:三大本地部署框架深度对比解析
  • ElasticSearch重置密码
  • LabVIEW浏览器ActiveX事件交互
  • JavaScript 性能优化实战:深入性能瓶颈,精炼优化技巧与最佳实践
  • aspnetcore Mvc配置选项中的ModelBindingMessageProvider
  • 多任务——协程
  • VictoriaMetrics 架构
  • VR样板间:房产营销新变革
  • 纯数学专业VS应用数学专业:这两个哪个就业面更广?
  • Cannot add property 0, object is not extensible
  • 【橘子分布式】Thrift RPC(理论篇)
  • iOS APP 上架流程:跨平台上架方案的协作实践记录
  • [Nagios Core] 通知系统 | 事件代理 | NEB模块,事件,回调
  • sqli-labs靶场通关笔记:第11-16关 POST请求注入
  • 迁移学习之图像预训练理解
  • 《大数据技术原理与应用》实验报告一 熟悉常用的Linux操作和Hadoop操作
  • OpenCV 视频处理与摄像头操作详解