什么微前端
微前端是一种测试方法,它为独立团队拥有的web应用提供多种功能或模块,使它们更加用户友好和更小的体积。他们基本上把前端应用分成独立的和半独立的微应用,这样每个应用都可以采用不同的技术,比如React、Angular或Vue,这样就可以很容易地集成到单个应用中。
为什么需要微前端
假设你正在一个项目中使用一个特定的框架或库(比如React.js),但你需要切换到另一个框架或库,或者添加另一个在另一个框架(比如Angular.js)上编写的模块。没有一个微前端,你将不得不重写整个项目或模块,这是一个乏味的过程。
另一种情况是,如果你正在处理一个包含多个团队的大型项目,那么协作将成为一项任务。当代码库很大时,组件和页面需要连接起来,因为有时您的工作与其他团队成员的工作重叠。这将导致进一步的重写,更复杂和时间管理不善,并导致整个开发过程的延迟。如果你不需要改变任何东西,你可以用你选择的另一个框架开始添加新模块呢?这就是微前端出现的地方。
它们帮助我们在多个框架(甚至是Vanilla JS)中编写应用程序,并使用相同的路由(router)和域(domain)加载它们。我们可以开发包含认证和路由实现的主父应用程序,然后我们可以继续添加多个独立工作的子应用程序,可以在相同或不同的页面加载。
如何构建微前端
现在让我们来看看如何构建一个真正的应用,以及如何使用微前端集成两个框架,React和Angular。这里出现的第一个问题是,我们应该如何划分应用,因为没有特定的标准来划分它们。根据我们的要求,我们可以用相当多的方式来做到这一点。让我们来看看下面的一些想法:
- 功能
这是最常用的划分,我们将在这里划分应用程序的特性或模块。例如,假设仪表板上有三个功能,我们也可以为每个各自的功能提供三个微前端,仪表板作为公共部分。
- 页面
在一些应用程序中,功能按页面划分。我们可以按页面来划分应用程序,使用这种方法时,每个页面都有独立的功能。
- 域
应用程序也可以按域划分。例如,我们可以根据我们的需求将应用程序划分为核心域、支付域或配置文件域。
在网页上实现子应用程序有两种方法:
- 每个页面上有一个应用程序
- 所有的子应用程序在一个页面上
准备
由于每个微前端将被放置在特定的位置,并将有自己的API,我们需要有一个将在特定位置呈现应用程序的基础。以下是一些我们可以实现这一目标的方法:
- Webpack module federation
- NGINX
- iFrames
- Web components
- H-include library
- Single SPA library
在这里,我们将专注于单一SPA库,因为它有如下功能:
- 延迟加载代码可以改善初始加载时间
- 在单个页面上使用多个框架
项目结构
我们将构建三个模块,即React中的主应用、React中的子应用和Angular中的子应用。我们可以用create-react-app来创建React的main-app、sub-app,用Angular CLI来在Angular中创建子app。
开始构建
我们将不得不使用某些函数在主应用程序中注册我们的子应用程序,以便导出我们的子应用程序。幸运的是,我们不需要手动实现这些函数,因为在Angular和React中,单个SPA可以自己处理这些函数。
- 子应用程序中的实现:
要将一个模块导出为一个子应用程序,我们必须导出以下生命周期函数:
bootstrap
——它将被调用一次,就在注册的应用程序第一次挂载之前。mount
-当注册的应用程序被挂载时,它将被调用。unmount
-当注册的应用程序被卸载时,这个函数将被调用。
我们还必须提供rootComponent
和domElementGetter
,其中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根模块)、domElementGetter
和template
。下面是实现Angular应用入口文件的代码片段:
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.angularApp
和 window.reactApp
中被访问。因为我们在两个子应用程序中都使用单个SPA函数,所以子应用程序和模板都将知道使用全局名称空间的single SPA生命周期函数的位置。
问题是如何设置这些子应用的位置?
要设置子应用程序的位置,只需在Webpack配置文件
中为每个子应用程序的module.exports.output
对象添加两个条目。你可以在下面的代码片段中看到Angular应用的例子(你也可以对React应用做同样的事情)。按照下面的例子:
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
:
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应用程序。
- 每个子应用程序可以在不同的堆栈上独立开发,当使用微前端时,可以由单个团队或多个团队拥有。
- 使用这种方法有许多优点,但请记住,这应该会使您的工作变得简单。它们不适合用于小型应用程序。
总结
微前端确实很强大,许多大型组织现在都在大规模地使用它,以使开发过程更加精简。它是一个开源资源,正在不断开发,并正在探索和测试以改进它。你可以将较小的应用组合起来,使用微前端创建大型前端应用,但将其应用于所有类型的应用是不明智的。理解您的应用程序可以让你更清楚地了解实现微前端的场景,以便以最好的方式利用它们的好处。