三年多一点前,我在 LogRocket 的一篇博文中揭示了通过某种发现机制形成微前端的想法,驯服前端单体。发现机制(称为提要服务)是我参与的解决方案的核心。
那个解决方案就是 Piral,我们在半年后的 O'Reilly 的软件架构会议上正式披露了它。
今天,Piral 是微前端领域最常用和知名的解决方案之一。仅此一项就可以证明另一篇博客文章的合理性——但是,我们也有越来越多的微前端流行,以及对可扩展性的总体需求。
事不宜迟,让我们看看微前端是什么,为什么松散耦合对它们如此重要,以及 Piral 如何解决这个(和其他问题)以实现出色的可扩展性。我们将在以下部分中介绍这一点:
什么是微前端?
其他微前端框架的可扩展性问题
皮拉尔有何不同?
使用 Piral 开发您的应用程序外壳
更改您的模板
更改提要以显示不同的堆
创建应用模拟器
推出微前端
换桩
构建和发布我们的pilet
集成 SWR 以执行 HTTP 请求
近年来,微前端越来越流行。原因之一是对大型 Web 应用程序的需求增加。如今,AWS 和 Azure 门户等功能强大的门户以及 Netflix 或 DAZN 等丰富的用户体验并非例外,而是常态。如何构建如此庞大的应用程序?如何缩放它们?
这些问题的一个答案可以是使用微前端。微前端是业务子域的技术表示。基本思想是单独开发一部分 UI。这件作品不需要在屏幕上的一个区域呈现;它实际上可以由多个片段组成,即一个菜单项和该菜单项链接到的页面。唯一的限制是该部分应与业务子域相关。
微前端由不同的组件组成,但这些不是经典的 UI 组件,如下拉菜单或富文本字段框。相反,这些组件是特定于域的组件,并包含一些业务逻辑,例如需要发出哪些 API 请求。
即使是像菜单项这样简单的东西在这个上下文中也是一个域组件,因为它已经知道到页面的链接来自同一个业务域。一旦组件具有一些领域逻辑,它就是一个领域组件——因此可以成为微前端的一部分。
为了实现微前端,存在一整套方法和实践。它们可以在构建时、服务器端和客户端组合在一起。
在本文中,我们将研究客户端的组合,但可以为服务器编写相同的故事。那么微前端实际上是如何扩展的呢?
许多微前端框架在现实环境中面临可扩展性问题。看其他文章,乍一看技术还不错;例如,如果您阅读使用 single-spa 创建微前端应用程序或使用 Podium 构建 Svelte 微前端,它们很好地介绍了技术和用例。另一个例子可以在使用 Fronts 构建渐进式微前端中看到。
问题是这些框架通常会尝试在视觉上分割 UI。但是,实际上,您永远不会将前端拆分为“导航”、“页眉”、“内容”和“页脚”等部分。这是为什么?
如上一节所述,一个真正的应用程序由来自不同子域的不同部分组成,这些子域组合在一起形成完整的应用程序域。虽然这些子域可以在纸上很好地完全分开,但它们通常在相同的布局元素中出现在最终用户面前。
想想像网上商店这样的东西。如果您有一个用于产品详细信息的子域和另一个处理先前订单的子域,那么您不希望在作为用户的订单历史记录中只看到无意义的产品 ID。相反,您希望订单历史记录中至少显示产品名称和一些详细信息。因此,这些子域在视觉上向最终用户交错。
超过 20 万开发人员使用 LogRocket 来创造更好的数字体验了解更多 →
同样,几乎每个子域都可以为共享的 UI 布局元素做出贡献,例如导航、页眉或页脚。因此,拥有专门处理导航区域的微前端在实践中没有多大意义,因为这个微前端会收到来自其他团队的大量请求——并成为瓶颈。这样做会产生一个隐藏的单体。
现在,有人可能会争辩说,在微前端中没有导航会导致对更改的相同需求,但这次是在应用程序外壳所有者上。这会更糟。
那么解决方案是什么呢?显然,我们需要将这些东西解耦。所以不要使用类似的东西:
import MyMenuItem1 from 'my-micro-frontend1';
import MyMenuItem1 from 'my-micro-frontend2';
import MyMenuItemN from 'my-micro-frontendN';
const MyMenu = () => (<>
);
我们需要注册每个必要的部分,例如来自微前端本身的导航项。这样,我们最终可以得到如下结构:
const MyMenu = () => {const items = useRegisteredMenuItems();
return (<>{items.map(({ id, Component }) =>
};
为了避免需要知道微前端的名称和位置,需要一种发现。这只是一个可以从已知位置(例如后端服务)检索的 JSON 文档。
现在我们知道我们需要扩展什么,是时候开始实施了。幸运的是,有一个框架可以让我们在这方面领先一步:Piral。
Piral是一个使用微前端创建超可扩展 Web 应用程序的框架。在许多方面,它具有以下特点:
集成的微前端发现机制
广泛的开发人员体验,可满足您所需的一切
具有跨框架支持的松散耦合组件
这样,各个团队可以专注于他们特定的领域问题,而不是需要对齐和联合发布。Piral 应用程序由三个部分组成:
应用程序外壳。这可以像包含对微前端发现服务(称为提要)的引用的 HTML 文件一样简单
微前端的提要(也可以是静态 JSON,但通常你会想要更强大的东西)
不同的模块(微前端),称为桩
整个设置可以绘制如下:
模块开发人员可以使用命令行实用程序piral-cli来搭建(即,使用一些模板创建)和发布新的 pilet,调试和更新现有模块,或者执行一些 linting 和验证。真正的用户不会将解决方案视为不同的部分——他们实际上是在一个应用程序中将应用程序外壳与堆砌一起使用。从技术上讲,这些桩是从饲料服务中获取的。
很多时候,微前端的开发体验并不理想。要么需要检查并开始多件事情,要么整个过程归结为一个开发-提交-尝试-失败-重启周期。Piral 在这里有所不同——它首先尝试离线。微前端直接在称为模拟器的应用程序外壳的特殊版本中开发。
模拟器只是一个 npm 包,可以安装在任何 npm 项目中。当piral-cli用于调试时,它实际上会将模拟器中的内容作为页面来显示。Pilet 将通过内部 API 提供服务,而不是使用远程提要服务或类似的东西。
尽管如此,在开发过程中加载现有的微前端可能仍然有意义。在这种情况下,仍然可以集成来自现有饲料的桩。
不要错过来自 LogRocket 的精选时事通讯The Replay
了解LogRocket 的 Galileo 如何消除噪音以主动解决应用程序中的问题
使用 React 的 useEffect优化应用程序的性能
在多个 Node 版本之间切换
了解如何使用 AnimXYZ 为您的 React 应用程序制作动画
探索 Tauri,一个用于构建二进制文件的新框架
比较NestJS 与 Express.js
让我们看看这一切在实践中是如何运作的。
有多种方法可以使用 Piral 创建应用程序外壳:
迁移现有项目
手动将包添加到新项目
使用piral-cli创建新项目
在这篇文章中,我们将做后者。
在命令行上,我们运行:
npm init piral-instance --bundler esbuild --target my-app-shell --defaults
my-app-shell这将在目录中创建一个新的应用程序外壳。该项目将使用 npm、TypeScript 和esbuild工具作为我们的捆绑器(尽管我们实际上可以选择任何类型的捆绑器,例如 webpack、Parcel 或 Vite 等)。在许多情况下,选择esbuild应该就足够了,并且可以提供最快的安装时间。
现在,我们可以开始调试项目了。进入新目录(例如,cd my-app-shell)并开始调试会话:
npm start
Going tohttp://localhost:1234应该向您展示标准模板:
我们现在可以在所有可能的方面更改模板。例如,我们可以将提供的布局更改为没有任何固定的内容图块;只需编辑src/layout.tsx文件并删除defaultTiles和defaultMenuItems. 确保不仅要删除它们的初始化,还要删除对它们的引用。
要获得更详细的信息,我们可以更改DashboardContainerfrom:
DashboardContainer: ({ children }) => (
Welcome to your new microfrontend app shell, built with:
至:
DashboardContainer: ({ children }) => (
Welcome to your new microfrontend app shell, built with:
这里可以看到的所有组件都有不同的用途。虽然其中许多来自可选插件,但有些——例如ErrorInfoor Layout——已经通过驱动 Piral 的核心库定义。
在上面的例子中,我们为 Piral 的仪表板插件定义了仪表板容器。仪表板插件为我们提供了一个仪表板,默认情况下,它位于/我们页面的主页 ( )。我们可以在这里更改所有内容,包括它的外观和位置(当然,如果我们想要一个仪表板)。
仪表板非常适合门户应用程序,因为它们在一个屏幕上收集大量信息。对于微前端,仪表板也很好——尤其是作为展示。在这个页面上,可能大多数(如果不是全部)微前端想要展示一些东西。
从仪表板容器中删除默认磁贴后,Web 应用程序现在应该看起来更空:
我们的 Web 应用程序空虚的主要原因是我们没有集成任何在某处呈现组件的微前端。此时,脚手架机制将我们的新应用程序外壳连接到 Piral 本身拥有的特殊提要:空提要。
空提要是一种提要,顾名思义,它将始终保持为空。我们可以更改应用程序外壳的代码以转到其他提要。
为此,您需要打开src/index.tsx文件。在那里,您将看到带有要使用的提要的 URL 的变量:
const feedUrl = &#39;https://feed.piral.cloud/api/v1/pilet/sample&#39;;
转到在另一个应用程序 shell中使用的示例提要,我们实际上可以看到如果由微前端正确填充 shell 的外观。仪表板现在应该如下所示:
我们已经可以在我们的新应用程序外壳中显示来自另一个提要的堆,这一事实实际上非常酷。这预先表明桩是真正独立的,并且与它们的外壳没有很强的关系。
但是,请记住,这种平滑的集成可能并不总是可行的。Pilets 总是可以以一种相当容易集成到其他 Piral 实例中的方式开发。同样,也可以以排除不同应用程序外壳的方式开发 pilet。
在我们为这个应用程序外壳开发一些微前端之前,我们需要创建它的模拟器。停止调试过程并运行以下命令:
npm run build
这将piral build在当前项目上运行。结果是在 中有两个子目录dist:
dist/release
dist/emulator
虽然前者可用于在某处实际部署我们的 Web 应用程序,但后者包含一个.tgz可以上传到注册表的文件,如下所示:
npm publish dist/emulator/my-app-shell-1.0.0.tgz
您可能需要 npm 凭据来发布包,但即使您已经登录到 npm,您也可能不想发布它,而是将其保密,或者在不同的注册表上发布。
要使用自定义注册表测试发布过程,您可以使用Verdaccio。在一个新的 shell 中,启动:
npx verdaccio
这将安装并运行 Verdaccio 的本地版本。您应该会在屏幕上看到类似以下内容:
warn --- http address - http://localhost:4873/ - verdaccio/5.13.1
转到此地址并查看说明。它们应该看起来像:
运行登录命令 ( ) 并填写数据。对于and ,您可以只使用. 什么都会被拿走;可以很简单。npm adduser --registry http://localhost:4873/Username
Passwordtest
Email``foo@bar.com
现在可以通过以下方式发布到自定义注册表:
npm publish dist/emulator/my-app-shell-1.0.0.tgz --registry http://localhost:4873/
完成后,我们可以为这个 shell 创建微前端!
正如我们对 app shell 所做的那样,我们可以使用piral-cli来搭建一个项目。这个命令现在使用pilet而不是piral-instance. 我们跑:
npm init pilet --source my-app-shell --registry http://localhost:4873/ --bundler esbuild --target my-pilet --defaults
这将创建一个名为的新目录my-pilet,其中包含新微前端的代码。工具设置为esbuild(像以前一样,我们使用 esbuild,因为它安装得非常快,但你也可以选择不同的东西,比如 webpack)。
上面的重要部分是指定--source,它表示用于开发的模拟器。现在一切就绪,我们可以cd my-pilet运行:
npm start
和以前一样,开发服务器托管在http://localhost:1234. 转到那里会产生如下所示的页面:
几乎就像我们使用了empty提要一样空。但是,在这种情况下,新 pilet 的模板已经注册了一个 tile 和一个菜单项。让我们看看如何改变这一点。
打开src/index.tsx文件并查看代码:
import * as React from &#39;react&#39;;
import type { PiletApi } from &#39;my-app-shell&#39;;export function setup(api: PiletApi) {api.showNotification(&#39;Hello from Piral!&#39;, {autoClose: 2000,});api.registerMenu(() =>Documentation);api.registerTile(() =>
简单来说,pilet 就是一个 Javascript 库;重要的部分是这个库导出的内容。
Pilet 导出一个setup函数(准确地说,还可以导出一个函数teardown)。该函数在微前端连接后使用,并接收单个参数 ,api该参数由应用程序外壳定义和创建。
app shell 的 API(通常称为 Pilet API)是 pilet 可以在应用程序中注册其部件的地方。让我们添加一个页面并稍微更改一下磁贴。
我们从瓷砖开始。我们可以给它一些类,比如teaser实际上有一点背景。此外,我们想为仪表板容器添加一些元数据。我们可以使用initialColumns和initialRows属性来传达所需的大小。
app.registerTile(() =>
保存后,该图块将具有一些不同的外观。让我们删除showNotification不再需要的并引入一个新页面:
api.registerPage(&#39;/foo&#39;, () =>
This is my page
要链接到此页面,我们可以更改已注册的菜单项。要执行 SPA 导航,我们可以使用熟悉的 React 工具react-router-dom:
api.registerMenu(() =>Foo
);
伟大的!然而,像页面这样的片段并不总是必需的,并且应该只在应该呈现它们时加载。这种延迟加载可以通过将代码放在一个专用文件中来实现,即,Page.tsx并将注册更改为:
const Page = React.lazy(() => import(&#39;./Page&#39;));
api.registerPage(&#39;/foo&#39;, Page);
的内容Page.tsx可以很简单:
import * as React from &#39;react&#39;;export default () => {return (<>Page Title
Lorem ipsum dolor sit ...
Lorem ipsum dolor sit ...
>);注册页面后,您现在可以单击导航栏中的“Foo”并查看页面:
现在我们的pilet已经写好了,我们可以实际构建和发布它了。在这一点上,我们还没有创建自己的提要或在任何地方发布应用程序外壳,所以最后一部分实际上有点理论。
要构建 pilet,您可以运行:
npm run build
创建后,您可以使用npx pilet pack. 这将与运行非常相似npm pack。结果是另一个.tgz文件——这次不是模拟器,而是实际的 pilet。小型电视怎么选?全部低于 43 英寸索尼、三星、LG等品牌的小型智能电视推荐tarball 是可以上传到专用服务(例如提要服务)的内容,提供应用程序外壳使用的提要。
可以在piral.cloud上找到非商业和商业产品的示例。
在结束本教程之前,让我们看看如何集成一个常用功能——在本例中,使用 SWR 执行 HTTP 请求。
有多种方法可以集成常见的问题,例如 SWR。将 swr(或位于其之上的其他库)添加到应用程序 shell 并在那里进行配置后,您有三个选项:
将其公开为共享库
通过 Pilet API 公开它
不暴露它(尽管在我们的 SWR 示例中,这不起作用,因为我们需要对 Hook 的引用)
将使用的决定swr留给 pilet:它们可以swr作为分布式依赖项共享(即,仅在没有其他 pilet 加载它时才加载它)
集成 SWT 最简单和最可靠的方法是使用第一个选项。为此,我们回到应用程序外壳。
在应用程序外壳的目录中运行:
npm install swr
现在,让我们修改package.json. 保留几乎所有内容,但修改该部分的externals数组,pilets如下所示:
{"name": "my-app-shell","version": "1.1.0",// ..."pilets": {"externals": ["swr"],// ...},// ...
}
请注意,我还更改了版本号。WhatsApp推出新功能,可发送带有标题的文档由于我们将对模拟器进行更新,因此我们需要一个新版本。这将指示 Piralswr与所有 pilet 共享依赖项。
为了实际测试这一点,让我们再次编写npm run build和发布。
npm run build
npm publish dist/emulator/my-app-shell-1.1.0.tgz --registry http://localhost:4873/
有了更新的 shell,让我们进入 pilet 的目录并升级应用程序 shell:
npx pilet upgrade
桩的package.json文件应该已经改变。它现在应该包含对my-app-shellin version1.1.0而不是1.0.0. 此外,您应该会看到和中swr列出的内容。devDependencies``peerDependencies
让我们修改要使用的页面swr:
import * as React from &#39;react&#39;;
import LogRocket from &#39;swr&#39;;
// note: fetcher could have also been globally configured in the app shell
// however, in general the pilet&#39;s don&#39;t know about this and so they may want to
// reconfigure or redefine it like here
const fetcher = (resource, init) => fetch(resource, init).then(res => res.json());
export default () => {const { data, error } = useSWR(&#39;https://jsonplaceholder.typicode.com/users/1&#39;, fetcher);
if (error) {return
现在我们完成了!不仅在我们的应用程序中成功设置了 SWR,我们还可以在所有微前端中使用它。这既节省了加载 SWR 的带宽,也节省了 SWR 的内部缓存,为所有微前端带来了不错的性能优势。
在这篇文章中,您已经看到了 Piral 入门是多么容易。确保Windows11电脑系统安全设置,防止危险或恶意软件破坏您的系统Piral 为您提供了将 Web 应用程序分发到不同存储库的选项,甚至跨不同的团队。
我们在这篇文章中只探讨了非常基本的设置,但您可以使用 Piral 做更多的事情。探索 Piral 的最佳方式是阅读官方文档。
Piral 比大多数其他解决方案具有更好的扩展性的原因是 Piral 鼓励松散耦合。这样,您将很难将两个东西融合在一起,这有助于您避免功能重叠和隐藏的单体。
无论您打算做什么,请确保已经考虑了要共享哪些依赖项以及要保留哪些依赖项。我们已经看到了一个示例,其中提供swr作为共享依赖项实际上是在几秒钟内设置的。快乐编码