跳转到主要内容
Chinese, Simplified

对于现代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,这里有一个提醒。享受! !

原文:https://medium.com/javascript-in-plain-english/create-micro-frontends-using-web-components-with-support-for-angular-and-react-2d6db18f557a

本文:http://pub.intelligentx.net/creating-micro-frontends-using-web-components-support-angular-and-react

讨论:请加入知识星球或者小红圈【首席架构师圈】

 

Article
知识星球
 
微信公众号
 
视频号