LoginSignup
9
4

More than 3 years have passed since last update.

Reactでインフラ構築してみたい

Last updated at Posted at 2019-12-17

最近、インフラ周りを触ってますが、ダッシュボードで各サービスの画面がバラバラに配置されているので、

サービスがどういう構成で動いているのか、どこでアラートが発生しているのかがわかりにくいんです :cry:

ふと思ったのは、みなさんがよく書くインフラ図がそのまま構築に使えたらわかりやすいのでは?です

近いイメージだと Cloudcraft ですね

もちろん、インフラの知識は必要ですが、インフラ構成とステータス情報がひと目でわかります :thumbsup:

この記事では、Custom React RendererでTerraformのconfiguration(JSON)を出力してみます :smile:

ユースケースは、Webで構成したCompoenntをJSONで出力してダウンロードしたものをTerraformで利用したりします

サンプルコードは一応動作しますが、機能不足のため参考程度でお願いします :pray:

React Rendererとは

ReactのComponentをあらゆる形式にrenderするもので、おなじみReactDOMやモバイル用のReactNativeなどがあります

他にもたくさんrendererが公開されています

参考: https://github.com/chentsulin/awesome-react-renderer

Reconciler

ReconcilerはStateが変更されたときの差分を検知し、検知すると特定のfunctionを呼び出します

この呼び出されるfunctionを定義するところが Host Config と呼ばれるObjectです

Host Configのfunctionの振る舞いを変更することで、Custom rendererを実装することができるのです :thumbsup:

reconcilerはpackage化されています

Custom rendererを自作する

index.js
import React from "react";
// import ReactDOM from "react-dom";
import TerraformRenderer from "./renderer";

// Root compoennt
const Config = props => <config>{props.children}</config>;

// Resource component
const Resource = props => (
  <resource name={props.name}>{props.children}</resource>
);

// Instance component
const AwsInstance = props => <instance {...props} />;

const App = () => {
  return (
    <Config>
      <Resource>
        <AwsInstance name="example" instance_type="t2.micro" ami="ami-abc123" />
      </Resource>
    </Config>
  );
};

TerraformRenderer.render(<App />, document.getElementById("root"));

index.jsはこんな感じですが、rendererを実装していないのでエラーになります

Custom renderを作成する

custom renderを作成します

/renderer.js
const TerraformRenderer = {
  render(element, renderDom) {
  }
};

export default TerraformRenderer;

これでひとまずエラーは表示されませんが、画面が真っ白、、

Elementのクラスを作成

React elementからインスタンスを作成するために、config, resource, instance element用のクラスを作成します

/elements/config.js
class Config {
  constructor(props) {
    this.props = props;
    this.children = [];
  }

  appendChild(child) {
    this.children.push(child);
  }

  render() {
    const config = this.children.reduce((acc, cur) => {
      const render = cur.render();
      return { ...acc, ...render };
    }, {});
    return { ...config };
  }
}
export default Config;
/elements/resource.js
class Resource {
  constructor(props) {
    this.props = props;
    this.children = [];
  }

  appendChild(child) {
    this.children.push(child);
  }

  render() {
    const resources = this.children.reduce((acc, cur) => {
      const render = cur.render();
      return { ...acc, ...render };
    }, {});
    return {
      resource: resources
    };
  }
}
export default Resource;
/elements/awsInstance.js
class AwsInstance {
  constructor(props) {
    this.props = props;
    this.children = [];
  }

  appendChild(child) {}

  render() {
    return {
      aws_instance: {
        [this.props.name]: {
          instance_type: this.props.instance_type,
          ami: this.props.ami
        }
      }
    };
  }
}
export default AwsInstance;

各クラスに appendChildrender functionを定義しておきます

Host configの修正

ここに独自のロジックを実装していきます

  • ポイント
    • createInstance のパラメータにReact elementのtypeとpropsがくるので対応するインスタンスを作成
    • appendInitialChild で親インスタンスに子インスタンスをセット
    • appendChildToContainer でrootコンテナにJSONをrender
/HostConf.js
import Config from './elements/config';
import Resource from './elements/resource';
import AwsInstance from './elements/awsInstance';

const constructors = {
  config: Config,
  resource: Resource,
  instance: AwsInstance
};

const HostConfig = {
  getRootHostContext: rootContainerInstance => {},
  prepareForCommit: containerInfo => {},
  resetAfterCommit: containerInfo => {},
  getChildHostContext: (parentHostContext, type, rootContainerInstance) => {},
  shouldSetTextContent: (type, props) => {},
  createInstance: (type, props) => {
    const { children, ...args } = props;
    return new constructors[type](args);
  },
  createTextInstance: (
    type,
    rootContainerInstance,
    hostContext,
    internalInstanceHandle
  ) => {},
  finalizeInitialChildren: (
    domElement,
    type,
    props,
    rootContainerInstance,
    hostContext
  ) => {},
  appendChildToContainer: (container, child) => {
    container.innerHTML = JSON.stringify(child.render());
  },
  appendChild: (parent, child) => {},
  appendInitialChild: (parentInstance, child) => {
    parentInstance.appendChild(child);
  },
  supportsMutation: true
};
export default HostConfig;

参考: https://github.com/facebook/react/blob/master/packages/react-reconciler/src/forks/ReactFiberHostConfig.custom.js

react-reconcilerをインストールします

$ npm install react-reconciler

最後にrendererを修正します

/renderer.js
import Reconciler from "react-reconciler";
import HostConfig from './HostConf';

const reconciler = Reconciler(HostConfig);

const TerraformRenderer = {
  render(element, renderDom) {
    const container = reconciler.createContainer(renderDom, false, false);
    reconciler.updateContainer(element, container, null, null);
  }
};

export default TerraformRenderer;

result.png

表示できました :tada:

まとめ

ひとまず、Custom rendererでJSONを画面に表示することができました :smiley:

ドキュメントや本体のコードを参考にしましたが、Custom rendererを実装することでReactの知識がさらに深まりました

あと、renderer周りを調べていて、役割毎にpackageが分割されてるんですよねー (とても参考になります

類似のサービスはあるし、APIで取得できる情報も限られていると思いますが、Reactでインフラ構築を今後も個人プロジェクトとしてやっていきですー :grin:

あと、テストも書かないとね、、 :smiling_imp:

こちらの動画もとても参考になりましたので、是非ご覧ください
React Conf 2019 Building a Custom React Renderer | Sophie Alpert

Enjoy Custom React Renderer ヘ(^o^)ノ

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4