[译]一种基于模块联邦的插件前端

news/2025/3/26 8:03:32/

f067cdb808db0f0d1d3ea783eeababcf.png

原文:https://malcolmkee.com/blog/a-plugin-based-frontend-with-module-federation/

在谈及模块联邦及其独立构建和部署的特性(通常称为微前端)时,一个常见的问题是,“为什么这比使用iframe更好?”虽然这的确是一个问题,特别是当只使用模块联邦拼接多个UI时,其好处可能不会立即显现的时候;答案就在于它无缝集成多个前端应用程序,并允许组件和函数调用一起工作的能力。这就是为什么模块联邦是目前构建微前端应用程序的最佳技术。

在本文中,我将为前端应用程序提供一个利用模块联邦的插件架构。该架构允许开发人员在既有应用程序中添加、删除或更新功能,而无需对应用程序进行任何更改。得益于模块联邦实现的无缝集成,该插件架构才成为可能。

插件架构是什么?

插件架构(plugin architecture)是一种软件架构,它允许 第三方开发者 通过编写插件来扩展现有软件的功能。

在插件系统中,“core”软件提供了 一组定义好的接口、API或钩子,以使开发人员在不修改核心软件的前提下添加新特性或修改应用程序的行为。这种方法促进了模块化,因为插件可以独立于核心软件开发,并且可以被轻松添加或删除以自定义应用程序。

插件系统通常用于需要大量定制的系统。例如,流行的软件,如浏览器,文本编辑器,构建工具和内容管理系统(CMS)都使用插件系统,使开发人员能够向软件添加新功能。VS Code 是一个流行的代码编辑器,它的扩展市场就是一个插件系统的例子。类似地,流行的 CMS WordPress 使用插件系统,使用户能够向其网站添加新功能。

以模块联邦实现的插件系统

模块联邦的一种典型模式包括一个单体应用程序(host),它从多个较小的应用程序(remote)中导入代码。host和remote都可以独立构建和部署,并且可以使用模块联邦在运行时将它们缝合在一起。

9b028c58b809c638c3a7e8148ad1a93d.png

将插件系统应用于模块联邦,可以使host应用程序或者说"core",在添加、更新或移除充当插件的remotes 时保持不变。唯一的约束是所有remote必须遵循一组定义好的接口或钩子

举个例子,假设所有remote应用都必须按照以下约定导出单个远程模块/register

// src/register.tsximport { register } from '@company/core-plugin';
import * as React from 'react';const OrdersPage = () => <h1>Orders</h1>;export default register({routes: [{path: 'orders',element: <OrdersPage />,},],
});

来自包@company/core-pluginregister函数是一个身份函数,用于强制类型安全:

import { RouteObject } from 'react-router-dom';export interface Plugin {routes: Array<RouteObject>;
}export const register = (plugin: Plugin) => plugin;

通过所有remote都使用该接口暴露的register模块,host就可以渲染已在所有remote上注册的全部路由:

//app.tsx in hostimport { Plugin } from '@company/core-plugin';
import * as React from 'react';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { createRoot } from 'react-dom/client';const getAllRemotes = () =>Promise.all([import('microfrontend1/register'),import('microfrontend2/register'),import('microfrontend3/register'),]) as Promise<Array<{ default: Plugin }>>;getAllRemotes().then((mods) => mods.map((mod) => mod.default)).then((remotes) => {const router = createBrowserRouter(remotes.map((remote) => remote.routes).flat());createRoot(document.getElementById('app')!).render(<RouterProvider router={router} />);});

如下例所示,每当在remote中增添新的路由,则host中无需改变单独的代码,只消在下次加载时便会自动出现了。

// src/register.tsximport { register } from '@company/core-plugin';
import * as React from 'react';const OrdersPage = () => <h1>Orders</h1>;
const OrdersDetailsPage = () => <h1>Orders Details</h1>;export default register({routes: [{path: 'orders',element: <OrdersPage />,},{path: 'orders/:orderId',element: <OrdersDetailsPage />,},],
});

可能的插件API

在模块联邦中的插件架构有了基本了解之后,你就可以通过创建更多的API或钩子来提高host的可扩展性了。下面是一些支持常见用例的插件API。请记住,它们不是详尽的,也不是必需的。可以根据你的用例来决定其取舍,或者也可以创建自己的API。

registerroutes 选项

这个选项在前面的部分中讨论过,是一个路由定义数组,通常可以从你使用的路由器库中扩展(在我的例子中,我重用了react-router-dom中的RouteObject接口)。它还可以包括子导航,比如在你的应用中要用tabs之类的时候。host将在构造其路由之前合并来自所有remote的路由定义。

从理论上讲,多个remote的路由可能会相互冲突,例如使用'*'这类过度贪婪的路径,当检测到这种情况时,你应该通过 linting 或控制台错误消息来缓解。

registernavItems 选项

也就是一个导航项目列表;你的host应用可能带有导航,此属性允许remote向其中添加/删除项目。该属性的可能定义为:

interface NavItem {path: string;label: string;/** 用去嵌套导航的章节或者说组 */section: string;/** 排序用 */order: number;/** 图标 */icon: React.ReactNode;/** 假设又区分了多种导航 */location: 'header' | 'footer' | 'sidebar';
}

结合了 <Slot /> 组件的 registerfills 选项

如果需要将组件从一个remote嵌入到另一个remote,这两个API可以帮上忙。

想象一个客户票证界面,它显示多个部分,如客户个人信息和过往订单等。客户票据界面由一个团队维护,而客户个人信息和订单由另一个团队开发,每个团队都维护着自己的remote应用。

7c4e7d51e70b0e3c182860b4ea2d7c6a.png

要将客户个人信息和过往订单嵌入到客户票证界面中,我们可以使用以下元素:

  1. 在客户票证界面(在 customer-support-app 那个 remote 应用里编写)中,渲染一个<Slot id="customerTicketScreen" />组件。就其本身而言,它什么也没有显示。

  2. 在客户个人数据和订单两个 remote 应用中,为 register 提供 fills选项

// src/register.tsxexport default register({fills: [{slotId: 'customerTicketScreen', // 匹配在 support 中由 Slot 提供的 idcomponent: PersonalInfoSection,},],
});
  1. 在 host 中,使用 React context 注入所有按 slotId 分组的 fills。在Slot组件中,读取 context 的值,并按照slotIdid匹配,渲染所有 fills。

usePluginEventEmitterusePluginEventListener

让来自多个 remote 的组件在同一个界面上共存,那么它们不可避免地要相互通信。usePluginEventEmitterusePluginEventListener 就是用于让组件发出/监听事件的自定义钩子。

从原理上来讲,这类钩子可以使用 mitt 事件总线或 window.dispatch(CustomEvent) 这样的自定义事件来实现。

总结

一个使用模块联邦的基于插件的前端架构,是创建复杂应用程序的强大方法,这样的应用允许来自多个项目的UI组件无缝集成。通过使用插件系统,开发人员可以在不修改host应用的前提下扩展其功能。

同时,虽然这种方法带来诸多便利,留意其潜在的挑战和走好钢丝也是很重要的。例如,如果要在多应用间复用工具函数或类,插件系统可能并不适用,反而 npm 包是个更好的选择。尽管有这些潜在限制,经过细心计划和实现,基于插件的前端架构还是可以为构建复杂应用提供一个灵活和可扩展的平台。

4ba1ecb2f3fc1c9edd5aa01bbe3ac638.png

b45616ed41520717d4aad5d550295189.png


http://www.ppmy.cn/news/1346595.html

相关文章

云计算关键技术

目录 一、云计算关键技术概述 1.1 概述 二、关键技术内容 2.1 虚拟化技术 2.2 分布式数据存储技术 2.3 资源管理技术 2.4 云计算平台管理技术 2.5 多租户隔离技术 2.5.1 多租户技术下SaaS 特征 2.5.2 多租户技术面临的技术难题 2.5.2.1 数据隔离 2.5.2.2 客户化配置…

ThinkPad X201 经典小黑 折腾玩

前段时间&#xff0c;在折腾ThinkPad T430时&#xff0c;偶然看到了ThinkPad X200&#xff0c;一个12.1英寸的高端便携小本。 想当年&#xff0c;但那是总裁级别才能用的&#xff0c;应该是接近2万元&#xff0c;我们是一直用DELL的。 没想到的是&#xff0c;在海鲜市场上&am…

大数据深度学习卷积神经网络CNN:CNN结构、训练与优化一文全解

文章目录 大数据深度学习卷积神经网络CNN&#xff1a;CNN结构、训练与优化一文全解一、引言1.1 背景和重要性1.2 卷积神经网络概述 二、卷积神经网络层介绍2.1 卷积操作卷积核与特征映射卷积核大小多通道卷积 步长与填充步长填充 空洞卷积&#xff08;Dilated Convolution&…

创建一个Vue项目(含npm install卡住不动的解决)

目录 1 安装Node.js 2 使用命令提示符窗口创建Vue 2.1 打开命令提示符窗口 2.2 初始Vue项目 2.2.1 npm init vuelatest 2.2.2 npm install 3 运行Vue项目 3.1 命令提示符窗口 3.2 VSCode运行项目 1 安装Node.js 可以看我的这篇文章《Node.js的安装》 2 使用命令提示…

1.0 Zookeeper 分布式配置服务教程

ZooKeeper 是 Apache 软件基金会的一个软件项目&#xff0c;它为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。 ZooKeeper 的架构通过冗余服务实现高可用性。 Zookeeper 的设计目标是将那些复杂且容易出错的分布式一致性服务封装起来&#xff0c;构成一个高…

《动手学深度学习(PyTorch版)》笔记7.2

注&#xff1a;书中对代码的讲解并不详细&#xff0c;本文对很多细节做了详细注释。另外&#xff0c;书上的源代码是在Jupyter Notebook上运行的&#xff0c;较为分散&#xff0c;本文将代码集中起来&#xff0c;并加以完善&#xff0c;全部用vscode在python 3.9.18下测试通过&…

由vscode自动升级导致的“终端可以ssh服务器,但是vscode无法连接服务器”

问题描述 简单来说就是&#xff0c;ssh配置没动&#xff0c;前两天还可以用vscode连接服务器&#xff0c;今天突然就连不上了&#xff0c;但是用本地终端ssh可以顺利连接。 连接情况 我的ssh配置如下&#xff1a; Host gpu3HostName aaaUser zwx现在直接在终端中进行ssh&am…

3分钟带你了解Vue3的nextTick()

前言 Vue 实现响应式并不是数据发生变化之后 DOM 立即变化&#xff0c;而是按一定的策略进行 DOM 的更新。简单来说&#xff0c;Vue在修改数据后&#xff0c;视图不会立刻更新&#xff0c;而是等同一事件循环中的所有数据变化完成之后&#xff0c;再统一进行视图更新&#xff…