用Fronts构建渐进式微前端

284 阅读9分钟

在过去的几年里,我们开发应用程序的方式已经发生了很大的变化。由于每天都有新的框架发布,拥有一个灵活的技术栈,例如,在React中构建应用程序的某些部分,在Vue中构建其他部分,在跨团队工作时是非常有益的。

微前端使我们能够建立单独开发和部署的网络应用,但作为一个单一的应用工作。微前端是实现更大灵活性的一种常见方法,允许团队合并用不同框架或库构建的组件。在生产中,有几种不同的方法来实现微前端。

在本教程中,我们将探讨构建微前端的几种方法。然后我们将深入研究Fronts.js,这是一个用于构建Web应用的渐进式微前端框架。让我们开始吧!

Fronts的替代品

首先,让我们回顾一下构建微前端的两种常见方法。 Web

单spa

Single Spa Router

single-spa 路由器使用一个single-spa root config 文件,该文件持有共享的依赖关系等信息,以及几个单独的 SPA 打包成模块,这些模块通过 API 互相通信并与 single-spa 通信

webpack模块联盟

另一个流行的解决方案是webpack的模块联盟,它被作为webpack v.5的核心功能之一引入。模块联盟解决了代码共享的问题,它允许应用程序从对方那里加载代码。然而,模块联盟有点啰嗦,而且缺乏自定义的选项。

开始使用Fronts

与其他微前端框架相比,Fronts提供了一个更完整、更有针对性的实现,提供了几个独特的优势,如对嵌套微前端的支持。嵌套的微前端是一个用一个代码库构建的前端,它被放在另一个用不同代码库构建的前端里面。嵌套式微前端允许一个父页面容纳几个不同的子页面,每个子页面都有可能使用不同的代码库构建。

此外,Fronts提供了跨框架支持,这意味着开发者不受限制地使用任何特定的技术栈。跨框架支持也允许开发者使用几种不同的技术来构建一个页面。代码拆分和懒惰加载是Fronts中包含的另外两个流行的功能,它们允许使用Fronts构建的不同应用程序作为模块导入不同的Fronts应用程序。

我们可以钩住Fronts应用程序所提供的生命周期方法,并执行所需的操作。最后,Fronts的API在某种程度上是通用的,与框架无关,这意味着我们可以将其应用于各种各样的用例。

Fronts的渐进性质

Fronts的本质是渐进式的,这意味着它同时支持模块联盟和非模块联盟。一个应用程序在没有模块联盟的情况下以正常模式开始,可以逐步转变为模块联盟。

除了模块联盟,Fronts还提供了一个版本控制模式,可以对不同的组件应用进行版本管理。

探究Fronts的API

正如我们之前提到的,Fronts的API是非常直接的。然而,值得深入了解一下它的代码。Fronts包括三种不同的加载器,根据需求选择正确的加载器。

当我们需要将CSS与其他应用程序隔离时,我们使用useWebComponents() API。对于那些不需要我们将CSS与其他应用程序隔离的应用程序,我们可以使用useApp() 载入器,然而,这样做是在选择的基础上进行的。最后,当我们需要将CSS和JavaScript与应用程序的其他部分隔离时,我们使用useIframe() 加载器。

除了这三个加载器,Fronts还包括两个用于模块联盟的API。createWebpackConfig() 是一个封装函数,它接收原始的webpack配置并返回支持模块联盟的更新的webpack配置。要生成模块联盟的webpack配置,只需调用createWebpackConfig()

getMeta() 是一个用来获取依赖关系图全貌的工具。调用getMeta() ,会返回整个monorepo结构的JSON,以及所有的组件应用和它们的依赖关系。

最后,Fronts为不同组件的微前端之间的通信提供了API。globalTransport.listen ,通过提供一个字符串事件和一个当该事件被触发的监听器来帮助配置全局事件监听器。

globalTransport.emit 函数会发出前一个函数将监听的事件。它接受一个字符串事件名称作为第一个参数,其值作为第二个参数。

使用Fronts的微型前端实例

现在我们了解了Fronts的基本原理,让我们通过一个例子来更深入地了解Fronts。

在我们的演示中,我们将在官方的fronts-examplerepo之上工作。容器中的电子商务网站是父站点,产品页面是子站点。为了跟上进度,你可以访问已完成的示例 repo。它的用户界面组件使用了Chakra UI,并有一个硬编码的products.json 文件和一个虚拟购物车。

我们最终的微观前台例子将看起来像下面的图片。

Micro Frontend Ecommerce Example

要运行该应用程序,请克隆软件库,然后运行以下命令。

yarn install

接下来,运行下面的代码。

start

要玩耍和测试该应用程序,请访问localhost

Fronts文件夹结构

上面的例子 Repo 包含两个成熟的 Fronts 项目,位于packages 目录内。请记住,没有必要将这两个项目放在同一个单行本内。如果每个项目都放在自己单独的资源库中,我们的应用程序将以完全相同的方式工作。

每个单独的应用程序的文件夹结构看起来大致像下面的代码段。

|- public
 |- index.html
|- src
    |- App.jsx
    |- index.jsx
    |- styles.css
    |- bootstrap.tsx 
|- .babelrc
|- webpack.config.js
|- site.json

让我们更详细地看看每个具体的文件。

bootstrap.tsx

顾名思义,我们将使用bootstrap.tsx 文件来帮助我们在浏览器中加载Fronts应用程序时进行引导。

export default function render(element: HTMLElement | null) {
  ReactDOM.render(<App />, element);
  return () => {
    ReactDOM.unmountComponentAtNode(element!);
  };
}
boot(render, document.getElementById('root'));

注意到应用程序是如何在一个渲染函数中被渲染的,然后该渲染函数被提供给Fronts库提供的boot 方法。

在这个函数中,React应用程序被渲染,就像我们对一个正常的React应用程序一样。它还返回一个箭头函数,当它不再需要时,会调用元素上的ReactDOM.unmount

作为Fronts库的用户,我们在bootstrap.tsx 文件中所需要做的就是调用启动方法,Fronts会处理剩下的事情。

site.json

site.json 是另一个重要的文件,它指定了应用程序的许多配置细节。下面是app1 ,容器电子商务应用程序的情况。

{
  "name": "app1",
  "dependencies": {
    "app2": "http://localhost:3002/remoteEntry.js"
  },
  "shared": {
    "react": { "singleton": true },
    "react-dom": { "singleton": true }
  }
}

注意到app2 ,产品应用,是如何通过传递localhost部署URL而被提到作为app1 的依赖关系之一的,其次是remoteEntry.js 作为入口。当我们把这两个应用部署到生产中时,remoteEntry.js 将被生产部署的 URL 所取代。

shared 对象提到了这些Fronts应用程序共享的所有依赖关系,这样就不会在浏览器上再次下载这些捆绑包。以这种格式指定入口点和共享的依赖关系,使得我们的捆绑功能与其他Fronts应用程序同步,而库将为我们处理其余的事情。

看一下app2 中的同一个文件,我们发现它看起来略有不同。

{
  "name": "app2",
  "exports": ["./src/bootstrap", "./src/Button"],
  "dependencies": {},
  "shared": {
    "react": { "singleton": true },
    "react-dom": { "singleton": true }
  }
}

特别是,我们看到一个额外的exports 关键。这是因为app2 输出了某些可以被其他应用程序使用的功能。在上面的例子中,src/bootstrap 文件被导出了,这意味着整个应用程序可以在其他Fronts应用程序中被导入,就像被app1

微型前端之间的路由

如果你看一下app1/src/App.tsx 文件,你会发现两个应用程序是如何共享路由的。

const routes = [
  {
    path: "/",
    component: () => <HomePage />,
    exact: true,
  },
  {
    path: "/app2",
   component: () => {
     const App2 = useApp({
       name: "app2",
     loader: () => import("app2/src/bootstrap"),
      });
      return <App2 />;
    },
    exact: true,
  },
];

路由/ 是为了主页,它由HomePage 组件处理,存在于app1 中。

Homepage Component Display

在上图中,主页按钮和购物车按钮来自app1 中的<Navigation /> 组件。整个粉色区域是由<HomePage /> 组件渲染的。

请注意,路由/app2 是由一个组件处理的,该组件使用useApp 的功能,从一个单独的微型前端生成一个组件。如果我们点击上一页的浏览产品按钮,我们会被带到产品页面,见下图。

Ecommerce App Gif

在浅灰色背景里面显示三个产品的应用是一个完全不同的应用,被Fronts.js无缝渲染。当我们看到对app2 的bootstrap函数的调用时,我们会看到网络标签里面的证明。

Final Fronts Micro Frontend Example

微型前端之间的通信

你可能已经注意到,在点击添加到购物车按钮时,显示在购物车图标顶部的数字会相应地增加或减少。鉴于这两个组件理论上是在两个不同的微前端,它们是如何进行通信的?

通信是通过Fronts库所提供的globalTransport 功能来实现的。在app1/src/App.tsx ,我们为增加和减少事件分别设置了两个全局监听器,并根据它修改购物车的数量。

const [count, setCount] = useState(0);
useEffect(
  () =>
    globalTransport.listen("increase", () => {
      setCount(count + 1);
    }),
  [count]
);
useEffect(
  () =>
    globalTransport.listen("decrease", () => {
      setCount(count - 1);
    }),
  [count]
);

app2/src/App.tsx 里面,当相应的按钮被点击时,我们将发出特定的事件。

function addToCart(pid) {
  const newProd = [...prod];
  newProd.forEach(p => {
    if (p.id === pid) {
      if (!p.active) {
        globalTransport.emit("increase");
        p.active = true;
      } else {
        globalTransport.emit("decrease");
        p.active = false;
      }
    }
  });
  setProducts(newProd);
}

Fronts确保在其中一个微前端(即发射器)中触发的事件,会在另一个微前端(即监听器)中调用相应的监听器。

与webpack的区别

Fronts库是在webpack的模块联盟基础上编写的,目的是为了简化构建微前端的过程。因此,Fronts包括了webpack开箱即用的所有优点。但是,当我们将两者并排比较时,我们发现Fronts有以下优势。

  • 减少了配置
  • 不需要处理插件的修改
  • 不需要处理原始的webpack配置
  • 简化的多应用路由
  • 可以选择CSS的边界级别

结论

微前端,即允许开发者单独处理前端应用程序的各个部分,并将其独立部署,继续蓬勃发展。Fronts真正的亮点是允许开发者团队对独立但相关的应用程序的生命周期进行解耦,而不必担心出现中断。

在我看来,Front的最小配置和它的灵活性是目前无可比拟的。它绝对值得考虑作为你下一个微前端项目的首选框架。我希望你喜欢这个教程。

The postBuild progressive micro-frontends with Frontsappeared first onLogRocket Blog.