マイクロフロントエンドでWeb Componentsを使う方法について

今回は以前紹介したWeb Componentsを使う方法について詳しく書いていこうと思います。

# 概要

[AngularとReactでWeb Componentsを使ってMicrofrontEndsを実装](https://medium.com/javascript-in-plain-english/create-micro-frontends-using-web-components-with-support-for-angular-and-react-2d6db18f557a)し、[Github](https://github.com/chriskitson/micro-frontends-with-web-components)に載せている方がいたのでそれを実装してみました。

先にコードを見た方が分かりやすいかもしれないです。

![e3ab0ca0-7b18-71f9-16ce-9660e5599d1c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483460/f1b874bf-d1ba-60d8-7c74-e9471079dc21.png "実装画面")


↑実装画面

構成、Usage、使われている技術を紹介します。

# 構成
![c5e80bf9-ff1f-d38f-9785-ceb77f7315f9.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483460/8e65d4ee-c005-990e-1b9c-cdd9ac2a2755.png)

**micro-fe-ng** 　…　Angularのコンポーネント

 **micro-fe-react** 　…　Reactのコンポーネント

**micro-fe-wrapper**　…　上の二つを統合する

# Usage

## Angularのコンポーネントにおいて
```
cd micro-fe-ng
npm i
npm i serve
npm start
```
とすると
http://localhost:5001/main.js
で起動します。


## Reactのコンポーネントにおいて
```
cd micro-fe-react
npm i
npm i serve
npm start
```
とすると
http://localhost:5002/
で起動します。

## Wrapperコンポーネントにおいて
```
cd micro-fe-wrapper
npm i
npm i serve
npm start
```
とすると
http://localhost:5000
で起動します。

![a012f4b0-840b-5e87-6057-38c73c881a15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/483460/5cc7dfcb-555b-d4a5-cba1-5ea5416fc0ea.png "それぞれを起動させるとターミナルはこんな感じ")


そして、
http://localhost:5000
を開けば先程の実装画面が表示されます。

# 使われている技術

## serve

Web Componentsを用いてMicrofrontEndsを実装するために、マイクロサービスとして静的なJavaScriptファイルを統合しなければならない。そこで、今回はローカル環境では**serve**を使う。
[serve - npm](https://www.npmjs.com/package/serve)

## Reactコンポーネントにおいて

Custom ElementのAPI `connectedCallback()` と　`disconnectedCallback()` を使うことによって、Reactのコンポーネントを`render()` と `unmount()` にマッピングできる。

```javascript:index.js
class MyCustomElement extends HTMLElement {
  constructor() {
    super();
  }
  connectedCallback() {
    ReactDOM.render(<MyReactComponent />, this);
  }
  disconnectedCallback(){
    ReactDOM.unmountComponentAtNode(this);
  }
}

customElements.define('react-el', ReactElement);
```

## Wrapper コンポーネントにおいて

CDNで**zone.js**と**custom-elements-es5-adapter.js**を読み込む

-  **zone.js** はAngularのために必要なもの
-  **custom-elements-es5-adapter.js** はブラウザ内でCustom Elementを使用するために必要。[Web Components - React](https://ja.reactjs.org/docs/web-components.html)（Reactの公式）でも記述されている。


AngularのコンポーネントとReactのコンポーネントも読み込む

```html:index.html
<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>
<!- 5001がAngularのコンポーネント、5002がReactのコンポーネント -!>
<script src="http://localhost:5001/main.js"></script>　
<script src="http://localhost:5002/main.js"></script>
```

以下がWeb Componentsが関わっている部分の大まかな全体像

```html:index.html
<body onload="tellComponents()">
  <div>...</div>
      <h5 class="card-header">Angular</h5>
      <div class="card-body" id="ng-container"></div>

      <h5 class="card-header">React</h5>
      <div class="card-body" id="react-container"></div>
  <script>
    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);

      logMessage('You', `my name is ${name}`)

    }

    function helloEvent(who) {
      logMessage(who, 'hello');
    }

    function logMessage(source, msg) {
      const msgContainer = document.getElementById('messages');
      msgContainer.innerHTML += `<p><strong>${source}</strong> said ${msg}`;
    }
  </script>
  <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>
</body>
```


## イベント(handler)や値をコンポーネント間で受け渡すとき(React)

propとして受け渡す

```javascript

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">
        <img src="/images/react.png" alt="React Logo" className="logo" />
        <p>Hello <strong>{name}</strong> from your friendly React component.</p>
        <button type="submit" className="btn btn-secondary" onClick={onHelloEvt}>Say hello</button>
      </div>
    )
  }
}
```

# 参考

[Creating Micro-frontends using Web Components (with support for Angular and React)](https://medium.com/javascript-in-plain-english/create-micro-frontends-using-web-components-with-support-for-angular-and-react-2d6db18f557a)
今回主に参考にしたサイト

[Github](https://github.com/chriskitson/micro-frontends-with-web-components)
伝わりにくい＆わかりにくいと思うので実際にコードを見るとわかりやすいと思います。

[Web Components - React](https://ja.reactjs.org/docs/web-components.html)

[serve - npm](https://www.npmjs.com/package/serve)

# デモ実装

Web Componentsを用いてマイクロフロントエンドのデモを実装してみました

https://qiita.com/kei37/items/554541fd9a5ed92d820e