微前端架构

本文详细介绍了微前端的概念、优势以及多种实现方式,包括iframe、服务端模板组合、single-spa和qiankun框架。微前端通过拆分应用,实现独立开发和部署,支持渐进式重构,降低了代码耦合。文中还展示了如何利用iframe进行通信,并探讨了不同微前端框架的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一. 什么是微前端

  • “微前端架构”就是构建基于微服务的前端应用架构

  • 其思想是将前端应用切分为一系列可以单独部署的松耦合的应用,然后将这些应用组装起来创建单个面向用户的应用程序

二. 微前端的优势

  1. 降低代码耦合
  2. 独立开发、独立部署
  3. 增量升级:微前端是一种非常好的实施渐进式重构的手段和策略
  4. 独立运行时,每个微应用之间状态隔离,运行时状态不共享
  5. 团队可以按照业务垂直拆分更高效

三. 微前端的多种实现

3.1 iframe

iframe 天然具备微前端的基因。我们只需将单体的前端应用,按照业务模块进行拆分,分别部署。最后通过 iframe 进行动态加载即可。

<html>
  <head>
    <title>微前端-ifame</title>
  </head>
  <body>
    <h1>我是容器</h1>
    <iframe id="mfeLoader"></iframe>
    <script type="text/javascript">
      const routes = {
        '/': 'https://app.com/index.html',
        '/app1': 'https://app1.com/index.html',
        '/app2': 'https://app2.com/index.html',
      };

      const iframe = document.querySelector('#mfeLoader');
      iframe.src = routes[window.location.pathname];
    </script>
  </body>
</html>

优点:

  • 实现简单
  • 天然具备隔离性

缺点:

  • 主页面和 iframe 共享最大允许的 HTTP 链接数。
  • iframe 阻塞主页面加载。
  • 浏览器的后退按钮无效

iframe子窗口调用父窗口的方法

在iframe的页面中,通过JavaScript编写代码来调用父组件的方法。可以使用window.parentwindow.top来引用父窗口对象,然后调用父窗口的方法。

例如,假设父组件中有一个名为"handleClick"的方法,可以在iframe的页面中使用以下代码来调用它:

window.parent.handleClick();
// 或者
window.top.handleClick();

iframe父窗口调用子窗口方法

在父窗口中,通过JavaScript获取iframe的引用,然后使用contentWindow属性访问子窗口的对象

var iframe = document.getElementById('myIframe');  
var childWindow = iframe.contentWindow;  
childWindow.handleClick();

iframe子窗口向父窗口通信

postMessage 用于在不同的域之间发送消息。它允许你发送消息到父窗口,并接收来自父窗口的消息。

在 iframe 中:

window.parent.postMessage('Hello from iframe!', 'http://example.com');

在父窗口中:

window.addEventListener('message', function(event) {  
    if (event.origin !== 'http://example.com') return;  // 验证消息来源  
    console.log('Received message from iframe:', event.data);  
}, false);

iframe的父窗口传递参数给子窗口

方法一:父窗口可以使用window.postMessage方法向子窗口发送消息

var iframe = document.getElementById('myIframe');  
iframe.contentWindow.postMessage('Hello from parent!', '*');

在子窗口中,可以使用以下代码监听消息:

window.addEventListener('message', function(event) {  
  if (event.origin !== 'http://example.com') return; // 验证消息来源  
  console.log('Received message from parent:', event.data);  
}, false);

方法二:使用URL查询参数:如果父窗口和子窗口处于同一域下,并且没有跨域限制,父窗口可以通过修改iframe的src属性,将参数作为URL查询参数传递给子窗口。在父窗口中,可以使用以下代码将参数作为URL查询参数传递给子窗口:

var iframe = document.getElementById('myIframe');  
iframe.src = 'child.html?param1=value1&param2=value2';

在子窗口中,可以通过JavaScript获取URL查询参数:

var param1 = getUrlParam('param1'); // 获取URL查询参数的方法,可以根据实际情况实现  
var param2 = getUrlParam('param2'); // 获取URL查询参数的方法,可以根据实际情况实现

3.2 服务端模板组合

常见的实现方式是,服务端根据路由动态渲染特定页面的模板文件。架构图如下:
在这里插入图片描述

优点:

  • 实现简单
  • 技术栈独立

缺点:

  • 需要额外配置 Nginx
  • 前后端分离不彻底

3.3 微前端框架 single-spa

在这里插入图片描述
借助 single-spa,开发者可以为不同的子应用使用不同的技术栈,比如子应用 A 使用 vue 开发,子应用 B 使用 react 开发,完全没有历史债务。

single-spa 的实现原理并不难,从架构上来讲可以分为两部分:子应用容器应用

子应用与传统的单页应用的区别在于:

  • 不需要 HTML 入口文件,
  • js 入口文件导出的模块,必须包括 bootstrap、mount 和 unmount 三个方法。

容器应用主要负责注册应用,当 url 命中子应用的路由时激活并挂载子应用,或者当子应用不处于激活状态时,将子应用从页面中移除卸载。其核心方法有两个:

  • registerApplication 注册并下载子应用
  • start 启动处于激活状态的子应用。

容器应用代码

<html>
<body>
    <script src="single-spa-config.js"></script>
</body>
</html>

single-spa-config.js 代码如下:

import * as singleSpa from 'single-spa';
const appName = 'app1';
const app1Url = 'http://app1.com/app1.js'

// loadJS 方法是伪代码,表示加载 app1.js。开发者需要自己实现,或者借助 systemJS 来实现。
singleSpa.registerApplication('app1',() => loadJS(app1Url), location => location.pathname.startsWith('/app1'))

singleSpa.start();

子应用代码:

//app1.js
let domEl;
export function bootstrap(props) {
    return Promise
        .resolve()
        .then(() => {
            domEl = document.createElement('div');
            domEl.id = 'app1';
            document.body.appendChild(domEl);
        });
}
export function mount(props) {
    return Promise
        .resolve()
        .then(() => {
            domEl.textContent = 'App 1 is mounted!'
        });
}
export function unmount(props) {
    return Promise
        .resolve()
        .then(() => {
            domEl.textContent = '';
        })
}

优点:

  • 纯前端解决方案
  • 可以使用多种技术栈
  • 完善的生态

缺点:

  • 上手成本高
  • 需要改造现有应用
  • 跨应用的联调变得复杂

3.4 微前端框架 qiankun

qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。

在主应用中注册微应用

import { registerMicroApps, start } from 'qiankun';

registerMicroApps([
  {
    name: 'react app', // app name registered
    entry: '//localhost:7100',
    container: '#yourContainer',
    activeRule: '/yourActiveRule',
  },
  {
    name: 'vue app',
    entry: { scripts: ['//localhost:7100/main.js'] },
    container: '#yourContainer2',
    activeRule: '/yourActiveRule2',
  },
]);

start();

当微应用信息注册完之后,一旦浏览器的 url 发生变化,便会自动触发 qiankun 的匹配逻辑,所有 activeRule 规则匹配上的微应用就会被插入到指定的 container 中,同时依次调用微应用暴露出的生命周期钩子。

微应用需要在自己的入口 js (通常就是你配置的 webpack 的 entry js) 导出 bootstrapmountunmount 三个生命周期钩子,以供主应用在适当的时机调用。

/**
 * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
 */
export async function bootstrap() {
  console.log('react app bootstraped');
}

/**
 * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
 */
export async function mount(props) {
  ReactDOM.render(<App />, props.container ? props.container.querySelector('#root') : document.getElementById('root'));
}

/**
 * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
 */
export async function unmount(props) {
  ReactDOM.unmountComponentAtNode(
    props.container ? props.container.querySelector('#root') : document.getElementById('root'),
  );
}

/**
 * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
 */
export async function update(props) {
  console.log('update props', props);
}

优点:

  • 简单:qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankun 的 API 即可完成应用的微前端改造
  • 解耦/技术栈无关
  • 完善的生态

缺点:

  • 上手成本高
  • 需要改造现有应用
  • 跨应用的联调变得复杂

3.5 微前端microApp

  1. main.ts中初始化micro-app和相关配置
/**
 * micro-app 微前端
 */
import microApp from '@micro-zoe/micro-app';
microApp.start({
  'disable-memory-router': true, // 关闭虚拟路由系统
  'disable-patch-request': true, // 关闭对子应用请求的拦截
  lifeCycles: {
    created() {
      // console.log('created');
    },
    beforemount() {
      // console.log('beforemount');
    },
    mounted() {
      console.log('fst', performance.now().toFixed()); // 首屏时间
      // console.log('mounted');
    },
    unmount() {
      console.log('unmount');
    },
    error(e) {
      Sentry.captureException(new Error('主站Error:生命周期错误'), {
        level: 'error',
        extra: {
          ...e
        }
      });
    }
  }
});
microApp.router.setBaseAppRouter(router);

microApp.router.beforeEach({
  'micoro-app-homeweb-app': (to, from) => {
    const toQuery = qs.parse(to.search, { ignoreQueryPrefix: true });
    const fromQuery = qs.parse(from.search, { ignoreQueryPrefix: true });

    if (
      toQuery.type !== fromQuery.type ||
      (from.pathname === '/search' && fromQuery.threadId && !toQuery.threadId && toQuery.type === 'chat')
    ) {
      // 设置全局ref
      savePageRef(to.fullPath);
    } else if (to.pathname === '/search' && toQuery.type === 'chat' && toQuery.threadId) {
      const BASE_URL = (import.meta as any).env.VITE_HOST;
      sessionStorage.setItem('ref', BASE_URL + to.fullPath);
    } else {
      savePageRef(to.fullPath);
    }
  }
});
microApp.router.afterEach({
  'micoro-app-homeweb-app': (to, from) => {
    const names = {
      '/': '首页',
      '/search': '搜索',
      '/g-star': 'g-star',
      '/g-star/apply': 'g-star 申请',
      '/explore': '搜索开源'
    };
    const toQuery = qs.parse(to.search, { ignoreQueryPrefix: true });
    if (to.pathname === '/search' && toQuery.type !== 'repo') {
      // 对话有threadid后上报
      if (toQuery.threadId) useReport('pageview', {}, { 'homeweb-page-title': names[to.pathname] });
    } else {
      useReport('pageview', {}, { 'homeweb-page-title': names[to.pathname] });
    }
  }
});
  1. 路由中添加micorApp名称
  {
    path: '/',
    component: DefaultLayout,
    children: [
      {
        path: '',
        name: 'home',
        component: () => import('@/views/micro-page/proxy-homeweb.vue'),
        meta: {
          micorApp: ['micoro-app-homeweb-app'],
          hasMobile: true,
          hiddenFooter: true,
          reportTitle: '首页',
          className: 'w-full min-w-full',
          headerClassName: ''
        }
      },
      ...
   ]
  }
  1. Layout.vue中添加相关路由守卫和监听器
microApp.router.afterEach({
  'micoro-app-homeweb-app': (to, from) => {
    outOfSearch.value = to.pathname !== '/search';
  }
});
const checkPage = (data: any) => {
  if (data.type === 'checkPage') {
    return 'ready';
  }
};
onMounted(() => {
  microApp.addDataListener('micoro-app-homeweb-app', checkPage, false);
});
onUnmounted(() => {
  microApp.removeDataListener('micoro-app-homeweb-app', checkPage);
});
  1. 页面中加载微应用
<template>
  <div id="homeweb-container"></div>
</template>

onMounted(() => {
  toolbarContain.value = document.getElementById('toolbarBottom');
  microApp.renderApp({
    name: 'micoro-app-homeweb-app',
    url: origin,
    container: '#homeweb-container',
    data: {
      emitter,
      toolbarContain,
      $router: router
    },
    iframe: true,
    baseroute: '/',
    defaultPtah: route.path
  });
});
onBeforeUnmount(() => {
  microApp.unmountApp('micoro-app-homeweb-app');
});
### 微前端架构概述 微前端架构是一种将大型单页应用(SPA)拆分成更小、独立的应用程序的方法,这些小程序可以由不同的团队并行开发、部署和维护[^4]。 ### 微前端架构的实现方式 #### 1. 基于 URL 路径划分 通过配置服务器或代理规则,根据请求路径的不同加载相应的子应用。这种方式简单直观,易于理解和实施。 #### 2. 使用 iframe 容器技术 利用 HTML `<iframe>`标签嵌入各个子页面,在父级页面中定义好布局结构后,动态调整 iframe 的 src 属性指向目标子站地址。此法能较好地解决跨域资源访问问题,但存在性能开销较大等问题。 #### 3. 单 SPA (Single-SPA) single-spa 是一种流行的 JavaScript 库,它允许在一个浏览器窗口内运行多个前端应用程序,并让它们协同工作。每个子应用都是一个完整的 Webpack 构建项目,拥有自己的入口文件、构建工具链等设置。当用户导航到特定路由时,single-spa 可以按需懒加载对应的子应用模块[^2]。 ```javascript // main.js - Single-SPA Root Configuration Example import { registerApplication, start } from 'single-spa'; registerApplication( '@your-org/app-name', () => System.import('@your-org/app-name'), location => location.pathname.startsWith('/app-name') ); start(); ``` #### 4. Qiankun 框架 Qiankun 是 Ant Design Pro 团队开源的一个基于 single-spa 的微前端解决方案。除了支持上述特性外,还提供了更加便捷的操作 API 和丰富的插件生态体系,帮助开发者快速搭建起稳定的生产环境。 ### 应用场景 对于那些规模庞大、业务复杂的企业级网站来说,采用微前端架构有助于: - **提升协作效率**:不同小组专注于各自负责的功能区域; - **简化运维流程**:各部分可单独更新迭代而不影响整体稳定性; - **增强用户体验一致性**:即使是由多支队伍共同完成的产品也能保持统一的设计风格和技术栈[^3]; ### 最佳实践建议 为了充分发挥微前端的价值,在实际操作过程中应当注意以下几点: - 明确职责边界,合理规划子项目的范围大小; - 维护一套通用的基础库供所有参与者调用; - 制定清晰的数据交换协议确保信息流通顺畅无阻; - 加强自动化测试覆盖度保障质量稳定可靠; - 推动 DevOps 文化建设加速反馈循环周期缩短交付时间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值