Skip to content
成为赞助商

微前端

  • 微前端的概念借鉴自后端的微服务,主要是为了解决大型工程在变更、维护、扩展等方面的困难而提出
  • 严格来讲,它们只是用于解决微前端中运行时容器的相关问题。除了运行时容器,一套完整的微前端方案还需要解决版本管理、质量管控、配置下发、线上监控、灰度发布、安全监测等与工程和平台相关的问题,而这些问题中的大部分工作目前仍处于探索阶段
  • 在微前端架构中,各个子应用可以基于不同的技术框架
  • 各个子应用将一些特定的业务功能封装在一个业务黑箱中,只对外暴露少量生命周期方法;基座应用根据路由地址变化,动态地加载对应的业务黑箱,并将其渲染到指定的占位DOM元素上
  • 微前端也可以一次加载多个业务黑箱,这称为多实例模式(类似于vue-router的命名视图)

主流微前端方案

  1. iframe:技术难度低,隔离性和兼容性很好,但是性能和使用体验比较差,多用于集成第三方系统;
  2. 基座模式,主要基于路由分发,qiankunsingle-spa就是基于这种模式
    • 主要基于路由分发,即由一个基座应用来监听路由,并按照路由规则来加载不同的应用,以实现应用间解耦;
  3. 组合式集成,即单独构建组件,按需加载,类似npm包的形式
  4. EMP,主要基于Webpack5 Module Federation
    • 基于Webpack5 Module Federation,一种去中心化的微前端实现方案,它不仅能很好地隔离应用,还可以轻松实现应用间的资源共享和通信;
  5. Web Components

无界 (wujie-micro.github.io)

js
// 这段代码是使用无界前端框架(WujieVue)创建的一个组件,并设置了一些属性和事件。

<!-- 
<WujieVue
    width="100%"
    height="100%"
    name="xxx"
    url="http://192.168.88.128:8001/"
    :sync="true"
    :fetch="fetch"
    :props="props"
    :beforeLoad="beforeLoad"
    :beforeMount="beforeMount"
    :afterMount="afterMount"
    :beforeUnmount="beforeUnmount"
    :afterUnmount="afterUnmount"
  ></WujieVue> 
-->

-  `width``height`  属性定义了组件的宽度和高度,分别设置为100%
-  `name`  属性定义了组件的名称,设置为"xxx"
-  `url`  属性定义了组件加载的URL地址,设置为"xxx"
-  `sync`  属性定义了组件是否同步加载,设置为true表示同步加载。
-  `fetch`  属性定义了组件加载数据的方法,可以自定义实现。
-  `props`  属性定义了组件的属性,可以传递给组件的数据。
-  `beforeLoad``beforeMount``afterMount``beforeUnmount``afterUnmount`  是组件的生命周期钩子函数,可以在相应的阶段执行一些操作。

无界

概念

实现方案
  • js 沙箱机制
  • 将子应用的js注入主应用同域的iframe中运行
  • iframe是一个原生的window沙箱,内部有完整的historylocation接口
  • 子应用实例instance运行在iframe中,路由也彻底和主应用解耦,可以直接在业务组件里面启动应用
  • css 沙箱机制
    • 采用webcomponent来实现页面的样式隔离,
    • 无界会创建一个wujie自定义元素,然后将子应用的完整结构渲染在内部
    • 子应用的实例instanceiframe内运行,dom在主应用容器下的webcomponent内,通过代理 iframedocumentwebcomponent,可以实现两者的互联
  • 通信机制 3种
    • props 注入
      • 子应用通过$wujie.props可以轻松拿到主应用注入的数据
    • window.parent 通信机制
      • 子应用iframe沙箱和主应用同源,子应用可通过window.parent通信
    • 去中心化的通信机制
      • 无界提供了EventBus实例,注入到主应用和子应用,所有的应用可以去中心化的进行通信

运行模式

单例模式

alive:false 需要进行子应用的 生命周期改造

  • 在单例式下,改变 url 子应用的路由会发生跳转到对应路由
ts
// 【生命周期改造】此处以vite为例,更多参看官网
declare global {
  interface Window {
    // 是否存在无界
    __POWERED_BY_WUJIE__?: boolean;
    // 子应用mount函数
    __WUJIE_MOUNT: () => void;
    // 子应用unmount函数
    __WUJIE_UNMOUNT: () => void;
    // 子应用无界实例
    __WUJIE: { mount: () => void };
  }
}

if (window.__POWERED_BY_WUJIE__) {
  let instance: any;
  window.__WUJIE_MOUNT = () => {
    const router = createRouter({ history: createWebHistory(), routes });
    instance = createApp(App)
    instance.use(router);
    instance.mount("#app");
  };
  window.__WUJIE_UNMOUNT = () => {
    instance.unmount();
  };
  /*
    由于vite是异步加载,而无界可能采用fiber执行机制
    所以mount的调用时机无法确认,框架调用时可能vite
    还没有加载回来,这里采用主动调用防止用没有mount
    无界mount函数内置标记,不用担心重复mount
  */
  window.__WUJIE.mount()
} else {
  createApp(App).use(createRouter({ history: createWebHistory(), routes })).mount("#app");
}
保活模式

alive:true

  • 改变 url 子应用的路由不会发生变化,需要采用 通信 的方式对子应用路由进行跳转
    • 保活状态下startApp无法更改子应用路由,不同菜单栏无法跳转到指定子应用路由,推荐单例模式
  • 子应用实例instancewebcomponent都不会销毁,子应用的状态和路由都不会丢失
重建模式

路由同步

路由同步会将子应用路径的path+query+hash通过window.encodeURIComponent编码后挂载在主应用url的查询参数上,其中key值为子应用的 name

确保 刷新浏览器或者将url分享出去子应用的路由状态都不会丢失;当一个页面存在多个子应用时无界支持所有子应用路由同步,浏览器刷新、前进、后退子应用路由状态也都不会丢失

路由跳转

主应用为 history 模式
vue
 <!-- 子应用间跳转  -->
 <!-- 主应用代码: -->
<template>
  <!-- 子应用 A -->
  <wujie-vue name="A" url="//hostA.com" :props="{jump}" ></WujieVue>
</template>

<script>
export default {
  methods: {
    jump(location) {
      this.$router.push(location);
    }
}
</script>

 <!--  直接打开其他子应用
子应用A  代码;  /pathB 为子应用B的路径 -->
window.$wujie?.props.jump({ path: "/pathB" });

 <!--  进入子应用的指定路由
要求:子应用 B 开启路由同步能力
注意这种办法只有在 B 应用未曾激活过才生效?
-->
window.$wujie?.props.jump({ path: "/pathB", query: { B: "/test" } });

 <!--  进入子应用的指定路由
子应用B已经激活过时:
-->
window.$wujie?.bus.$emit("routeChange", "/test");  // 子应用A 执行跳转

window.$wujie?.bus.$on("routeChange", (path) => this.$router.push({ path }));	// 子应用 B 监听并跳转






 <!--【主应用为 hash 模式 】 子应用间跳转  -->
主应用为hash模式

当主应用为 hash 模式时,主应用路由的 query 参数会挂载到 hash 的值后面,而无界路由同步读取的是 url 的 query 查询参数,所以需要手动的挂载查询参数

vue
 <!--  直接打开其他子应用【同上】
子应用A  代码;  /pathB 为子应用B的路径 -->
window.$wujie?.props.jump({ path: "/pathB" });

 <!--  进入子应用的指定路由【子应用未实例化过】 -->
<!-- 1.主应用 的 jump 修改: -->
<template>
  <wujie-vue name="A" url="//hostA.com" :props="{jump}" ></wujie-vue>
</template>

<script>
export default {
  methods: {
    jump(location, query) {
      // 跳转到主应用B页面
      this.$router.push(location);
      const url = new URL(window.location.href);
      url.search = query
      // 手动的挂载url查询参数
      window.history.replaceState(null, "", url.href);
    }
}
</script>
<!-- 2.子应用 B 开启路由同步能力 -->
<!-- 3.子应用的点击跳转函数: -->
function handleJump() {
  window.$wujie?.props.jump({ path: "/pathB" } , `?B=${window.encodeURIComponent("/test")}`});
}

<!-- 子应用为保活应用,【实例化过】  同上  -->

通信方式

props通信
js
// 主应用·通过props注入数据/方法
<WujieVue name="xxx" url="xxx" :props="{ data: xxx, methods: xxx }"></WujieVue>

// 子应用通过 $wujie 获取
const props = window.$wujie?.props; // {data: xxx, methods: xxx}
window 通信

由于子应用运行的iframesrc和主应用是同域的,所以相互可以直接通信

js
// 主应用调用子应用的全局数据
window.document.querySelector("iframe[name=子应用id]").contentWindow.xxx;

// 子应用调用主应用的全局数据
window.parent.xxx;
eventBus [推荐]

无界提供一套去中心化的通信方案,主应用和子应用、子应用和子应用都可以通过这种方式方便的进行通信, 详见 api

js
// 主应用中
// 如果使用wujie
import { bus } from "wujie";

// 如果使用wujie-vue
import WujieVue from "wujie-vue";
const { bus } = WujieVue;

// 如果使用wujie-react
import WujieReact from "wujie-react";
const { bus } = WujieReact;

// 主应用监听事件
bus.$on("事件名字", function (arg1, arg2, ...) {});
// 主应用发送事件
bus.$emit("事件名字", arg1, arg2, ...);
// 主应用取消事件监听
bus.$off("事件名字", function (arg1, arg2, ...) {});



// 子应用中
// 子应用监听事件
window.$wujie?.bus.$on("事件名字", function (arg1, arg2, ...) {});
// 子应用发送事件
window.$wujie?.bus.$emit("事件名字", arg1, arg2, ...);
// 子应用取消事件监听
window.$wujie?.bus.$off("事件名字", function (arg1, arg2, ...) {});

主应用

js
import { bus, setupApp, preloadApp, startApp, destroyApp } from "wujie";

/*  setupApp    设置子应用[可选],由于preloadApp和startApp参数重复,为了避免重复输入,可以通过setupApp来设置默认参数。*/


/*  preloadApp    预加载 */
type preOptions  {
  /** 唯一性用户必须保证 */
  name: string;
  /** 需要渲染的url */
  url: string;
  /** 需要渲染的html, 如果用户已有则无需从url请求 */
  html?: string;
  /** 注入给子应用的数据 */
  props?: { [key: string]: any };
  /** 自定义运行iframe的属性 */
  attrs?: { [key: string]: any };
  /** 自定义降级渲染iframe的属性 */
  degradeAttrs?: { [key: string]: any };
  /** 代码替换钩子 */
  replace?: (code: string) => string;
  /** 自定义fetch,资源和接口 */
  fetch?: (input: RequestInfo, init?: RequestInit) => Promise<Response>;
  /** 子应用保活模式,state不会丢失 */
  alive?: boolean;
  /** 预执行模式 */
  exec?: boolean;	 // exec:true, 开启预加载
  /** js采用fiber模式执行 */
  fiber?: boolean;
  /** 子应用采用降级iframe方案 */
  degrade?: boolean;
  /** 子应插件 */
  plugins: Array<plugin>;
  /** 子应用生命周期 */
  beforeLoad?: lifecycle;
  /** 没有做生命周期改造的子应用不会调用 */
  beforeMount?: lifecycle;
  afterMount?: lifecycle;
  beforeUnmount?: lifecycle;
  afterUnmount?: lifecycle;
  /** 非保活应用不会调用 */
  activated?: lifecycle;
  deactivated?: lifecycle;
  /** 子应用资源加载失败后调用 */
  loadError?: loadErrorHandler
};



/*  startApp    启动子应用 */

子应用

ServerComponents

  • Next.js

Astro 服务器岛

image-20241119191859864

访客总数 总访问量统计始于2024.10.29