无需框架,就能实现微前端,理解起来通俗易懂

2022-07-29 08:11:03 浏览数 (1)

什么微前端

微前端是一种测试方法,它为独立团队拥有的web应用提供多种功能或模块,使它们更加用户友好和更小的体积。他们基本上把前端应用分成独立的和半独立的微应用,这样每个应用都可以采用不同的技术,比如React、Angular或Vue,这样就可以很容易地集成到单个应用中

为什么需要微前端

假设你正在一个项目中使用一个特定的框架或库(比如React.js),但你需要切换到另一个框架或库,或者添加另一个在另一个框架(比如Angular.js)上编写的模块。没有一个微前端,你将不得不重写整个项目或模块,这是一个乏味的过程。

另一种情况是,如果你正在处理一个包含多个团队的大型项目,那么协作将成为一项任务。当代码库很大时,组件和页面需要连接起来,因为有时您的工作与其他团队成员的工作重叠。这将导致进一步的重写,更复杂和时间管理不善,并导致整个开发过程的延迟。如果你不需要改变任何东西,你可以用你选择的另一个框架开始添加新模块呢?这就是微前端出现的地方。

它们帮助我们在多个框架(甚至是Vanilla JS)中编写应用程序,并使用相同的路由(router)和域(domain)加载它们。我们可以开发包含认证和路由实现的主父应用程序,然后我们可以继续添加多个独立工作的子应用程序,可以在相同或不同的页面加载。

如何构建微前端

现在让我们来看看如何构建一个真正的应用,以及如何使用微前端集成两个框架,React和Angular。这里出现的第一个问题是,我们应该如何划分应用,因为没有特定的标准来划分它们。根据我们的要求,我们可以用相当多的方式来做到这一点。让我们来看看下面的一些想法:

  • 功能

这是最常用的划分,我们将在这里划分应用程序的特性或模块。例如,假设仪表板上有三个功能,我们也可以为每个各自的功能提供三个微前端,仪表板作为公共部分。

  • 页面

在一些应用程序中,功能按页面划分。我们可以按页面来划分应用程序,使用这种方法时,每个页面都有独立的功能。

应用程序也可以按域划分。例如,我们可以根据我们的需求将应用程序划分为核心域、支付域或配置文件域。

在网页上实现子应用程序有两种方法:

  1. 每个页面上有一个应用程序
  2. 所有的子应用程序在一个页面上

准备

由于每个微前端将被放置在特定的位置,并将有自己的API,我们需要有一个将在特定位置呈现应用程序的基础。以下是一些我们可以实现这一目标的方法:

  • Webpack module federation
  • NGINX
  • iFrames
  • Web components
  • H-include library
  • Single SPA library

在这里,我们将专注于单一SPA库,因为它有如下功能:

  1. 延迟加载代码可以改善初始加载时间
  2. 在单个页面上使用多个框架
项目结构

我们将构建三个模块,即React中的主应用、React中的子应用和Angular中的子应用。我们可以用create-react-app来创建React的main-app、sub-app,用Angular CLI来在Angular中创建子app。

开始构建

我们将不得不使用某些函数在主应用程序中注册我们的子应用程序,以便导出我们的子应用程序。幸运的是,我们不需要手动实现这些函数,因为在Angular和React中,单个SPA可以自己处理这些函数。

  1. 子应用程序中的实现:

要将一个模块导出为一个子应用程序,我们必须导出以下生命周期函数:

  • bootstrap——它将被调用一次,就在注册的应用程序第一次挂载之前。
  • mount -当注册的应用程序被挂载时,它将被调用。
  • unmount -当注册的应用程序被卸载时,这个函数将被调用。

我们还必须提供rootComponentdomElementGetter,其中rootComponent用于渲染React应用,在这种情况下,后者返回应用渲染到的DOM元素

下面是实现React应用入口文件的代码片段:

代码语言:javascript复制
import React from 'react';
import ReactDOM from 'react-dom';
import singleSpaReact from 'single-spa-react';
import rootComponent from './App';

const reactLifecycles = singleSpaReact({
    React,
    ReactDOM,
    rootComponent,
    domElementGetter: () => document.getElementById('react-app')
});

export const bootstrap = [
    reactLifecycles.bootstrap,
];

export const mount = [
    reactLifecycles.mount,
];

export const unmount = [
    reactLifecycles.unmount,
];

我们必须在Angular文件中为single-spa-angular提供一个mainModule*(Angular根模块)、domElementGettertemplate。下面是实现Angular应用入口文件的代码片段:

代码语言:javascript复制
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app/app.module';
import singleSpaAngular2 from 'single-spa-angular2';


const ng2Lifecycles = singleSpaAngular2({
  domElementGetter: () => document.getElementById('angular-app'),
  mainModule: AppModule,
  angularPlatform: platformBrowserDynamic(),
  template: `<app-root />`,
});

export const bootstrap = [
  ng2Lifecycles.bootstrap,
];

export const mount = [
  ng2Lifecycles.mount,
];

export const unmount = [
  ng2Lifecycles.unmount,
];

现在我们已经准备好了子应用程序,但是你必须考虑主应用程序如何找到引导、挂载和卸载函数。一旦这两个子应用程序都被执行,这些函数应该自动在一个 window.angularAppwindow.reactApp中被访问。因为我们在两个子应用程序中都使用单个SPA函数,所以子应用程序和模板都将知道使用全局名称空间的single SPA生命周期函数的位置。

问题是如何设置这些子应用的位置?

要设置子应用程序的位置,只需在Webpack配置文件中为每个子应用程序的module.exports.output对象添加两个条目。你可以在下面的代码片段中看到Angular应用的例子(你也可以对React应用做同样的事情)。按照下面的例子:

代码语言:javascript复制
  module.exports = {
    output: {
      library: "angularApp",
      libraryTarget: "window"
    }
  };

实现主应用程序

  • 将单个SPA库添加到package.json文件。
  • 在一个SPA库中注册所有的子应用程序。

下面给出了启动SPA文件、registerReactApp和registerAngularApp的代码片段:

代码语言:javascript复制
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as singleSpa from 'single-spa';
import {registerReactApp} from "./apps/react-app";
import {registerAngularApp} from "./apps/angular-app";

ReactDOM.render(<App/>, document.getElementById('root'));


registerReactApp();
registerAngularApp();

singleSpa.start();

使用下面的代码片段注册registerReactApp:

代码语言:javascript复制
import * as singleSpa from "single-spa";
import {matchingPathname, runScript} from "./utils";

const loadReactApp = async () => {
    await runScript('http://localhost:3002/static/js/main.js');
    return window.reactApp;
};


export const registerReactApp = () => {
    singleSpa.registerApplication('react-app', loadReactApp, matchingPathname(['/react', '/']));
};

使用下面的代码片段注册registerAngularApp:

代码语言:javascript复制
import * as singleSpa from "single-spa";
import {matchingPathname, runScript} from "./utils";

const loadAngularApp = async () => {
    await runScript('http://localhost:3001/inline.bundle.js');
    await runScript('http://localhost:3001/polyfills.bundle.js');
    await runScript('http://localhost:3001/styles.bundle.js');
    await runScript('http://localhost:3001/vendor.bundle.js');
    await runScript('http://localhost:3001/main.bundle.js');
    return window.angularApp;
};


export const registerAngularApp = () => {
    singleSpa.registerApplication(
        'angular-app', 
        loadAngularApp, 
        matchingPathname(['/angular', '/'])
    );
};

你一定已经注意到,子应用程序和子应用程序都将要求知道子应用程序容器的DOM元素ID。这时候你就必须考虑如何在应用程序之间实现通信系统

通信

这里的子应用程序彼此是完全独立的,但我们可以通过使用像 eev 事件总线这样的库让它们在某些事件上相互通信。eev事件总线是一个小而快速的零依赖事件发射器,它可以帮助我们在React和Angular应用之间交换信息。要了解更多关于这个发射器,请点击这里。您可以使用本机web浏览器事件机制进行通信,而且它不需要任何额外的库。

归纳

  • 在许多情况下,微前端简化了开发,它们基本上是前端微服务的实现。
  • 通过使用微前端,我们可以让它更容易理解、开发、测试和部署大型应用程序,即使是复杂的web应用程序。
  • 每个子应用程序可以在不同的堆栈上独立开发,当使用微前端时,可以由单个团队或多个团队拥有。
  • 使用这种方法有许多优点,但请记住,这应该会使您的工作变得简单。它们不适合用于小型应用程序。

总结

微前端确实很强大,许多大型组织现在都在大规模地使用它,以使开发过程更加精简。它是一个开源资源,正在不断开发,并正在探索和测试以改进它。你可以将较小的应用组合起来,使用微前端创建大型前端应用,但将其应用于所有类型的应用是不明智的。理解您的应用程序可以让你更清楚地了解实现微前端的场景,以便以最好的方式利用它们的好处。

0 人点赞