【微前端】使用Web组件创建微前端(支持Angular和React)
对于现代UI开发人员,您的“选择框架”是您内心深处的东西,它以某种方式定义了您的身份,并且可能限制了您解决问题的能力。就像我之前说的,如果每个人都和睦相处不是更好吗?
如果您是React或Angular、Ember或Vue,让我们创建一个地方,让它们都可以使用web组件和谐地生活在一起。
使用web组件作为Angular和React组件的包装器,我将展示它们在单个应用程序中协同工作的情况。我还将把数据从父容器传递到两个组件,然后再返回。我们的成品会是这样的:
先决条件
为了让我们能够专注于重要的部分,我把所有的东西都编码并上传到我的github:
https://github.com/chriskitson/micro-frontends-with-web-components?sour…---------------------------
无论如何,这都不是一个深入的研究,但是我将浏览Angular中web组件支持的重要部分并作出反应。
*如果您想完成其他一些框架(Vue, Ember等),请随意在我的repo上创建一个pull request !
我已经使用Angular CLI为我们的Angular项目生成了一个起点。你至少应该熟悉这个工具,这样你就可以浏览代码:
https://cli.angular.io/?source=post_page---------------------------
由于我们的最终目标是web组件,所以我们将把静态JavaScript文件组合成微型服务。为此,我将在我的本地环境中使用serve,这是一个使用node服务静态文件的优秀工具:
https://www.npmjs.com/package/serve?source=post_page---------------------------
Angular 组件作为自定义元素
由于Angular似乎正在采用web组件(带有Angular元素),您可以将Angular组件作为web组件来使用,只需对Angular CLI生成的默认项目做一些小调整。
在/micro-fe-ng目录下,一切都应该正常工作,你需要做的就是安装依赖项并运行启动脚本:
cd micro-fe-ng/
npm i
npm start
现在我们的Angular微前端将自定义元素定义为,应该在http://localhost:5001/main.js上运行。
注意:我们在不同的端口上通过localhost提供文件,但是它们很容易位于共享相同DNS的多个微服务中。
如果你对这是如何实现的感兴趣,这里是一个必要的变化纲要:
我们需要一些新的依赖:
Angular对自定义元素的支持(@angular/elements)和ngx-build-plus,后者是Angular的另一种构建工具(这对Angular元素有很好的支持):
npm i @angular/elements ngx-build-plus -D
我们还需要对包做一些修改。json来构建我们的Angular项目,并作为自定义元素服务于我们的项目:
micro-fe-ng / package.json:
"start": "npm run build && serve -l 5001 dist/micro-fe-ng",
"build": "ng build --prod --output-hashing none --single-bundle true",
我们需要在app.module中定义自定义元素。ts:
micro-fe-ng / src / app / app.module.ts:
import { BrowserModule } from '@angular/platform-browser'; import { NgModule, Injector } from '@angular/core'; import { createCustomElement } from '@angular/elements';import { AppComponent } from './app.component'; import { CustomelementComponent } from './customelement/customelement.component';@NgModule({ declarations: [ AppComponent, CustomelementComponent ], imports: [ BrowserModule ], providers: [], bootstrap: [], entryComponents: [ AppComponent, CustomelementComponent ] })export class AppModule { constructor(private injector: Injector) {} ngDoBootstrap(): void { const { injector } = this; // create custom elements from angular components const ngCustomElement = createCustomElement(CustomelementComponent, { injector }); // define in browser registry customElements.define('ng-el', ngCustomElement); }}
最后,我们需要告诉Angular使用ngx-build-plus构建工具,方法是在Angular内部的三个位置指定它。json如下图所示:
Ngx-build-plus将构建的项目作为一个JS文件返回,这是web组件作为一个服务工作的要求。
micro-fe-ng / angular.json:
..."architect": { "build": { "builder": "ngx-build-plus:build", .... "serve": { "builder": "ngx-build-plus:dev-server", ... "test": { "builder": "ngx-build-plus:karma",
将react组件作为自定义元素
由于React对web组件没有开箱即用的支持,我们将不得不编写比以前多一点的代码来包装一个标准的React组件,并将其呈现为一个本地web组件(自定义元素)。
与React组件非常相似,定制元素(web组件)也有生命周期钩子,您可以通过回调方法访问这些钩子。
通过使用定制元素API的connectedCallback()和disconnectedCallback()生命周期方法,我们可以将它们分别映射为render()和unmount()我们的React组件,如下所示:
class MyCustomElement extends HTMLElement { constructor() { super(); } connectedCallback() { ReactDOM.render(<MyReactComponent />, this); } disconnectedCallback(){ ReactDOM.unmountComponentAtNode(this); } }
通过映射React道具和事件,我将这一阶段做得更深。如果您想查看它,请查看/micro-fe-react/src/index.js。
在示例存储库中,所有东西都应该很好地工作,这样您就可以执行以下操作来启动和运行React微服务:
cd micro-fe-react/
npm i
npm start
现在我们的React微前端将定制元素定义为< React -el />,应该在http://localhost:5002/main.js上运行
Micro-frontend包装
我们有两个微前端服务;一个用于Angular组件,一个用于React组件。
现在让我们来创造一个他们可以一起生活的世界…
在/micro-fe-wrapper目录下,一切都应该正常工作,你所需要做的就是安装依赖项并运行开始脚本:
cd micro-fe-wrapper/
npm i
npm start
现在,我们的微前端包装器应该运行在http://localhost:5000。
要了解它是如何工作的,请继续阅读……
由于web组件是原生HTML规范的一部分,所以我们不需要做太多花哨的工作来将它们组合在一起。
在现实世界中,您可能想要使用一个框架来实现更好的代码结构和数据绑定等,但是为了简单起见,我们只使用普通的HTML/JS。
micro-fe-wrapper / index . html:
我们需要包括一些来自CDN的外部依赖:
- zone.js 是Angular所需要的。在包装器应用程序中包含一次是很好的实践,因为不能在同一个页面上有多个版本。
- custom-elements-es5-adapter.js 在浏览器中提供自定义元素支持。
此外,我们应该包括来自组件服务的JS文件,我们在前面的步骤中建立和部署:
<script src="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.9.1/zone.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/2.2.10/custom-elements-es5-adapter.js"></script> <script src="http://localhost:5001/main.js"></script> <script src="http://localhost:5002/main.js"></script>
我已经定义了一个方法tellComponents(),它应该将自定义元素标记注入到我们的页面中: <ng-el /> 代表Angular, < React -el />代表React。
我还使用setAttribute()传递一个属性名,以模拟包装器应用程序与组件的对话。
我还使用addEventListener()侦听一个名为helloEvt的事件,该事件将侦听来自组件的事件,使它们能够与父应用程序和其他组件通信。很酷!
React和Angular之间helloEvt()的属性名略有不同。这是由于框架之间的约定不同造成的。我待会再解释……
function tellComponents() { const name = document.getElementById('yourName').value; const reactEl = document.createElement('react-el'); reactEl.setAttribute('name', name); reactEl.setAttribute('onHelloEvt', 'onHelloEvt'); reactEl.addEventListener('onHelloEvt', (e) => helloEvent('react')); const reactElContainer = document.getElementById('react-container') if (reactElContainer.children.length > 0) { reactElContainer.removeChild(reactElContainer.children[0]); } reactElContainer.appendChild(reactEl); const ngEl = document.createElement('ng-el'); ngEl.setAttribute('name', name); ngEl.addEventListener('helloEvt', (e) => helloEvent('angular')); const ngElContainer = document.getElementById('ng-container'); if (ngElContainer.children.length > 0) { ngElContainer.removeChild(ngElContainer.children[0]); } ngElContainer.appendChild(ngEl); }
向组件传递值和从组件传递值
还记得我们传递给自定义元素的name属性吗?实际上,在组件中读取这个值非常简单。
在Angular中,我们只是简单地引用一个输入:
export class CustomelementComponent implements OnInit {
@Input() name: string;
...
}
这使得我们的模板中的值可用:
<p>Hello <strong>{{name}}</strong> from your friendly Angular component.</p>
在React中,它将作为一个道具传递给组件:
export class ExampleComponent extends React.Component { static propTypes = { name: PropTypes.string } static defaultProps = { name: "Chris" } render() { const { name } = this.props; return ( <div className="exampleComponent"> <p>Hello <strong>{name}</strong> from your friendly React component.</p> </div> ) } }
从组件发送事件几乎与监听helloEvt一样简单。
在Angular中,我们只需要指定一个输出:
export class CustomelementComponent implements OnInit {
@Input() name: string;
@Output() helloEvt: EventEmitter<string> = new EventEmitter();
...
}
然后我们可以从模板中调用这个事件:
<button type="submit" (click)="helloEvt.next()">Say hello</button>
注意,EventEmitter在Angular中创建了一个可观察对象,因此我们需要调用next()。
在React中,我们的组件包装器(micro-fe-react/src/index.js)将查找前缀为“on”的道具,并将它们视为事件,例如onClick()、onFocus()等原生事件。这就是为什么我们将定制事件onHelloEvt()称为React。
事件在React中被视为道具,所以我们需要做的就是定义道具并将其调用为onClick()处理程序。就是这样!
export class ExampleComponent extends React.Component { static propTypes = { name: PropTypes.string, onHelloEvt: PropTypes.func } static defaultProps = { name: "Chris" } render() { const { name, onHelloEvt } = this.props; return ( <div className="exampleComponent"> <button type="submit" onClick={onHelloEvt}>Say hello</button> </div> ) } }
结论
使用这些概念,您应该能够创建一些真正强大的应用程序,通过使用Web组件自定义元素规范混合Angular和React组件。
为什么混合框架是有益的还是有问题的(取决于您的用例),有很多利弊;考虑适应性、可伸缩性、性能、安全性、资源分配、浏览器支持等因素。
如果你还没有查看我的github,这里有一个提醒。享受! !
本文:http://pub.intelligentx.net/creating-micro-frontends-using-web-components-support-angular-and-react
讨论:请加入知识星球或者小红圈【首席架构师圈】
- 145 次浏览