Amplify Studio と Figma を同期してReactコンポーネントを自動生成してみた

# はじめに
[Metaps Advent Calendar](https://qiita.com/advent-calendar/2023/metaps) 第25日目の記事です。
（弊社の場合、Advent Calendarの投稿日は参加してるエンジニア同士で早い者勝ちで決まるのですが、もたもたしてたら最終日のクリスマス当日枠になってしまいました🎄）

本題に入りますが、Figmaで作成されたデザインからReactコンポーネントを自動生成できれば便利ですよね。

今回は、ずっと気になっていた「Amplify Studio」と「Figma」を同期してReactコンポーネントを自動生成する方法を試してみました。FigmaのデザインからReactコンポーネントに変換してReactアプリに組み込む手順について主に書いていこうと思います。この[ハンズオン](https://aws.amazon.com/jp/blogs/news/aws-amplify-studio-figma-to-fullstack-react-app-with-minimal-programming/)に沿って進めていきます。

### 事前準備
- Githubアカウント
- AWSアカウント
- Figmaアカウント（無料版でOK）

## Amplifyアプリケーションのデプロイ
バックエンドとフロントエンドを1つのGUI開発環境でビルドするために、まずは[コチラ](https://console.aws.amazon.com/amplify/home#/deploy?repo=https://github.com/renebrandel/amplify-homes)からGitHubのサンプルリポジトリをフォークして、構成済みのバックエンドが含まれた新しいAmplifyアプリケーションをデプロイします。

![スクリーンショット 2023-12-24 10.52.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/278ab6d2-d917-0174-5268-d0b6601ff771.png)

Githubに接続したら以下のような画面が表示されるので、好きな名前をつけていきます。
今回は`amplify-homes`というアプリ名で生成しました。
ここでサービスロールを作成する必要があるので、「新しいロールを作成」を押してIAMからロールを作成していきます。
![スクリーンショット 2023-12-24 10.55.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/35a6659c-e901-f5ce-33e7-7a135477afbe.png)

![スクリーンショット 2023-12-24 17.38.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/bba621df-c73b-fab4-4fa0-10cd0fc464c6.png)


ロール作成ができたら、そのロールを選択して「保存してデプロイ」をします。
![スクリーンショット 2023-12-24 11.03.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/5856fd10-ffe8-dd76-009a-85445ed2af8e.png)


この画面にたどり着くのに5分以上はかかるので、次の手順などを読みながら待つのが良さそうです。
![スクリーンショット 2023-12-24 11.18.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/55f9de0b-6b11-41b0-05d4-82a562182e01.png)

デプロイが完了したら、`Backend environments`タブに移動して、「Studio を起動する」を押します。
![スクリーンショット 2023-12-24 11.24.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/4de228e3-6f41-55fe-835a-e7a5624de691.png)

## Amplify Studio Consoleでシードデータの作成

別タブで以下のAmplify Studioコンソールが起動します。ここからFigmaからインポートしたUI Libraryの設定やシードデータの生成などが操作できます。

![スクリーンショット 2023-12-24 11.29.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/a55dd81b-a76b-a0d7-3a9c-c41111d1b53d.png)

早速、サイドメニューの`Data`から`Data Modeling`の画面をひらいてシードデータを作成していきます。今回は、`ID`, `address`, `image_url`, `price`のフィールドを持つデータを定義していきます。

![スクリーンショット 2023-12-24 17.37.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/222b23f0-c6fe-ad37-9ee2-771efe710454.png)


Actions > Auto-generate dataからデータを作成すると、データをいくつ作成するかや、数字の範囲、値の設定などが選択できます。

`address`フィールドには住所を入れたいので、「Street address」を設定します。
`image_url` は[Unsplash's random photo generator](https://images.unsplash.com/photo-1703097259971-e2e98aaa45f6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwxfDB8MXxyYW5kb218MHx8fHx8fHx8MTcwMzQwODA3MQ&ixlib=rb-4.0.3&q=80&w=1080)を使いましょうとのことなので、それに従って生成された画像のURLを入力していきます。

![スクリーンショット 2023-12-24 11.36.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/f5d5a843-2b68-b8cd-8b11-de3f8df2bf55.png)

そのほか、データ自動生成時に何かしらの制約を追加したい場合は `Add constrain`から追加が可能です。

![スクリーンショット 2023-12-24 11.38.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/d3839346-d6f2-d49b-6539-4eda5f827582.png)

`Generate data`を押してデータの生成が完了！！
ここで生成したデータをUI構築時に使用できるようになります。

![スクリーンショット 2023-12-24 17.50.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/2149dc14-0556-467e-278a-e6d0ec281ea1.png)

## Figma でデザインテンプレートをインポート
自分でデザインを生成しても問題ないですが、せっかくなので[AWS Amplify UI Kit](https://www.figma.com/community/file/1047600760128127424)テンプレートを使ってみることにします。
このUI Kit でFigma を開いて、まずはREADMEに目を通してみます。

![スクリーンショット 2023-12-24 18.03.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/04c3179c-a579-402a-c390-13ffbf58b8e6.png)
Getting Startedの章で`Primitives`の中身は触るな！！と言われているので、このpageは眺めるだけにしましょう。
ここはAmplify UIでpre-buildされたprimitivesが含まれている箇所なので、このページでデザインに変更を加えたとしても、Amplify Studioに反映されないと言われてますね。（いじくる前にREADME見ていて良かった..）

自分でデザインを作成する場合は、`My Components` page なら大丈夫そうですね！
primitives page を少し覗いてみると、色んなボタンはもちろん`SearchField`とか`SliderField`なども用意されてました。

![スクリーンショット 2023-12-24 18.25.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/a81ad327-1e5b-0ace-866d-baf82ce93761.png)

![スクリーンショット 2023-12-24 18.25.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/ef65fc4c-508d-96d8-3839-53c67aef1ed6.png)

## Figma でデザインを作成
Figma でデザインの作成をしたことないので、見よう見真似でチュートリアルで紹介されていたデザインを作ってみることにします。 READMEでコンポーネントを作成するには`YOU HAVE TO USE FRAMES` と記載されているので、frameをまずは作成していきます。

※ Figma上で自分のアカウントの権限が`Read Only`になっていてFrame作成のボタンが表示されなかったので注意

![スクリーンショット 2023-12-24 18.32.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/3c8f0cb0-9bb7-d4f7-e7af-5cbd064091e4.png)

`HomeCard`という名前でコンポーネント化し、その中にさらに`frame`を入れて、`Title`と`Description`を作成していきます。

Figmaの基本的な操作方法を調べながらできたデザインがコチラ。

![スクリーンショット 2023-12-24 13.14.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/ffbc8ff4-cd0a-04fc-92e2-0d542368d7b3.png)


## Figma と Amplify Studio を同期
Figmaで作成したコンポーネントをAmplify Studioに取り込むために同期の設定をしていきます。まずはFigmaの右上の`Share`ボタンを押して、URLをコピーします。

![スクリーンショット 2023-12-24 12.11.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/f97a4265-43e2-dd5e-fc67-cc300f338c6a.png)

Amplify Studio に戻り、サイドメニューの`UI Library`から右上の`Sync with Figma`を押し、コピーしたFigmaのURLをAmplify Studio に貼り付けて同期させます。

![スクリーンショット 2023-12-24 12.13.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/90a82af2-aece-1b62-fc91-d05deb6d46a4.png)


![スクリーンショット 2023-12-24 12.13.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/7a523027-d49d-c9a3-68c6-c8a287d29417.png)

コピーしたURLを貼り付けると、AmplifyがFigmaへのアクセス権を求めるウィンドウが立ち上がるので、許可をします。
![スクリーンショット 2023-12-24 12.14.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/d27f39ff-5fb1-51d0-b754-9f6e5d329157.png)

Amplify Studio の`UI Library`にFigmaのデザインがComponentsとして作成されていれば成功です！自作した`HomeCard`コンポーネントもAmplify Studioに取り込まれてるのが下記の画像から確認できますね。


## UI コンポーネントをデータと紐づける
`UI Library`にFigmaでインポートしたデザインが`Components`として表示されるので、今回自分で作成した`HomeCard`コンポーネントを選択して`Configure`を押します。


![スクリーンショット 2023-12-24 19.06.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/8b9a40bc-5bd8-0e7d-fbbb-fb3d93278b64.png)

ここで開いたUIコンポーネントエディターで、コンポーネントのプロパティと、それに紐づくコンポーネントのUI要素を紐づけていきます。

`Component properties`には`Add prop`から入力フィールドを追加し、Data Modeling画面で作成した`Home`を`Type`に指定します。 `Child properties`にはUIコンポーネントの各要素に紐付けたいデータを指定していきます。

`Elements tree`で操作したい要素を選択して、どのデータを紐づけるかを直感的に操作することができました。


`image要素`
![スクリーンショット 2023-12-24 13.28.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/270f9c4c-b4fe-f224-45e6-0adbb36ca2b6.png)

`description要素`
![スクリーンショット 2023-12-24 13.32.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/387853c6-19fa-7ed8-574c-c1b70654ebb3.png)

今回は、要素の`Title`には`home.address`、`Description`には`home.price`を文字列の中で変数を展開して表示するように割り当てます。 また、`onClick action`も割り当てができるので、画像を押したら画像URLに遷移するような動作も実現することができそうです。

これでコンポーネントにデータの紐付けは完了です！

## Collectionのコンポーネントを作成する
実際にUIを構築するときには、このコンポーネントを利用して複数の`HomeCard`を表示させたいので、コレクションを作成してみます。上記キャプチャの右上の`Create collection`からコレクション名を入力して生成されたコンポーネントが以下になります。 これでシードデータを使用した`HomeCardCollection`コンポーネントの作成ができました。画面左側の`Layout`で確認できますが、`Grid`表示でのカラム数の指定、X-aligin, Y-align や`HomeCard`を並べるときの余白も`px`で指定することが可能です。

![スクリーンショット 2023-12-24 13.36.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/ac9cca6d-dc4d-15b7-9211-a6f9e0234051.png)

## Reactアプリにコンポーネントを取り込む
上記のComponent Editor 画面で`</>Get component code`を押すと、Reactアプリに取り込む手順が表示されるので、それに従えばコンポーネントを取り込むことができます。

![スクリーンショット 2023-12-24 20.01.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/8a88942f-3dca-13aa-d250-6538389397e0.png)

今回はReactアプリをまだ作成してないので、以下のコマンドを実行して`amplify-homes`という名前でReactアプリを作成します。

```bash
npx create-react-app amplify-homes	
```

次にnpmコマンドを実行します。

```bash
npm install -g @aws-amplify/cli
```

```bash
npm install aws-amplify @aws-amplify/ui-react
```
`aws-amplify/ui-react`は、AWS AmplifyのReact用UIコンポーネントライブラリです。このライブラリには、認証コンポーネント（ログイン、サインアップフォームなど）や、様々なUI要素が含まれており、Reactアプリケーションでの使用に最適化されているようです。

次に以下のコマンドを実行します。
```bash
amplify pull --appId {YOUR_APP_ID} --envName devh
```
実行すると、Amplifyにログインされるかを聞かれ、その後、FigmaのコンポーネントをReactのコンポーネントに変換するためにいくつか質問が表示されるので、回答していきます。

![スクリーンショット 2023-12-24 14.57.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/f9b56981-43e7-4da3-5ad6-610e1a1c5978.png)

![スクリーンショット 2023-12-24 14.58.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/2bef47ec-38d1-f1ef-9d94-f3bb9e6f8e16.png)

### すると、コンポーネントが自動生成されました！！:sunglasses:

`amplify-homes`のプロジェクトを確認すると、`amplify`, `/src/ui-components`ディレクトリが追加されてます。そして、`ui-components`の配下には、Amplify Studio から実際にPullしてきたコンポーネントの一覧が格納されてます。Amplify Studio の`UI Library > Components`に存在するコンポーネント名と一致するファイルが生成されてることが確認できます。フロント実装時には、ここからコンポーネントをインポートして使用する形になります。

![スクリーンショット 2023-12-24 20.43.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/eb112176-b9b3-c894-54ae-15311787306f.png)


次に`index.js`を修正します。

```diff_javascript:index.js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";

+ import { ThemeProvider } from "@aws-amplify/ui-react";
+ import { Amplify } from "aws-amplify";
+ import awsconfig from "./aws-exports";

+ import "@aws-amplify/ui-react/styles.css";
+ import { studioTheme } from "./ui-components";

+ Amplify.configure(awsconfig);

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
+     <ThemeProvider theme={studioTheme}>
      <App />
+     </ThemeProvider>
  </React.StrictMode>
);

reportWebVitals();

```

`App.js`で出力されたコンポーネントをインポートします。テンプレートに存在していた`NavBarHeader2`もインポートしてみました。[Amplify UI](https://ui.docs.amplify.aws/) の「Flex」コンポーネントで使用できるプロパティを使用できるようです。 この例では試しに、`NavBarHeader2`に`width={"100vw"}`を指定してみました。

```react:App.js
import "./App.css";
import { HomeCardCollection, NavBarHeader2 } from "./ui-components";

function App() {
  return (
    <div className="App">
      <NavBarHeader2 width={"100vw"} />
      <HomeCardCollection />
    </div>
  );
}

export default App;

```

`HomeCardCollection`に`isPaginated`プロパティと`itemsPerPage={3}`などを指定することで、ページネーションを表示できたり1ページに表示させる`HomeCard`の数などを指定することが可能です。
```react
<HomeCardCollection isPaginated itemsPerPage={3}/>
```

### アプリ起動!!
```bash
npm start
```

![スクリーンショット 2023-12-24 16.00.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185271/0dc98f66-0bdd-58fd-f1ad-10d5d39ed569.png)

FigmaでデザインしたものにAmplify Studio で生成したデータが紐づいた形でUIが構築できてますね！ イイ感じです！

## まとめ
今回、特に詰まった点はなく、GUIからぽちぽちするだけで、Reactコンポーネントを自動生成できる点は非常に便利と感じました。デザインからコンポーネントを作る作業が爆速になりそうな予感がしました！！ただ実際にFigmaでデザインしてReactの自動生成をしてみた結果、Figmaの知識が少し必要かも、、という所感です。プロジェクトに導入するなら、Figmaでデザイン生成時点でデザイナーと開発者の間でしっかり決め事を決めておかないと想定外のことも起きそうな気がしました。ネックになりそうなポイントを洗い出して検討する必要はありそうかもという感じですね。

最後に出力されたコードを貼り付けて貼り付けておわりにしたいと思います。
### Figmaから自動生成されたコンポーネントのjsxファイルのコード

```react:HomeCard.jsx
/***************************************************************************
 * The contents of this file were generated with Amplify Studio.           *
 * Please refrain from making any modifications to this file.              *
 * Any changes to this file will be overwritten when running amplify pull. *
 **************************************************************************/

/* eslint-disable */
import * as React from "react";
import { getOverrideProps } from "./utils";
import { Image, Text, View } from "@aws-amplify/ui-react";
export default function HomeCard(props) {
  const { home, overrides, ...rest } = props;
  return (
    <View
      width="320px"
      height="235px"
      display="block"
      gap="unset"
      alignItems="unset"
      justifyContent="unset"
      overflow="hidden"
      position="relative"
      padding="0px 0px 0px 0px"
      backgroundColor="rgba(255,255,255,1)"
      {...getOverrideProps(overrides, "HomeCard")}
      {...rest}
    >
      <Image
        width="320px"
        height="160px"
        display="block"
        gap="unset"
        alignItems="unset"
        justifyContent="unset"
        position="absolute"
        top="0px"
        left="0px"
        padding="0px 0px 0px 0px"
        objectFit="cover"
        src={home?.image_url}
        {...getOverrideProps(overrides, "image")}
      ></Image>
      <View
        width="320px"
        height="67px"
        display="block"
        gap="unset"
        alignItems="unset"
        justifyContent="unset"
        overflow="hidden"
        position="absolute"
        top="168px"
        left="0px"
        padding="0px 0px 0px 0px"
        backgroundColor="rgba(255,255,255,1)"
        {...getOverrideProps(overrides, "Frame 437")}
      >
        <Text
          fontFamily="Inter"
          fontSize="16px"
          fontWeight="700"
          color="rgba(0,0,0,1)"
          lineHeight="24px"
          textAlign="left"
          display="block"
          direction="column"
          justifyContent="unset"
          width="320px"
          height="25px"
          gap="unset"
          alignItems="unset"
          position="absolute"
          top="0px"
          left="0px"
          padding="0px 0px 0px 0px"
          whiteSpace="pre-wrap"
          children={home?.address}
          {...getOverrideProps(overrides, "Title")}
        ></Text>
        <Text
          fontFamily="Inter"
          fontSize="12px"
          fontWeight="400"
          color="rgba(0,0,0,1)"
          lineHeight="24px"
          textAlign="left"
          display="block"
          direction="column"
          justifyContent="unset"
          width="320px"
          height="25px"
          gap="unset"
          alignItems="unset"
          position="absolute"
          top="33px"
          left="0px"
          padding="0px 0px 0px 0px"
          whiteSpace="pre-wrap"
          children={`${"Price: $"}${home?.price}${"/night"}`}
          {...getOverrideProps(overrides, "Description")}
        ></Text>
      </View>
    </View>
  );
}
```

```react:HomeCardCollection.jsx
/***************************************************************************
 * The contents of this file were generated with Amplify Studio.           *
 * Please refrain from making any modifications to this file.              *
 * Any changes to this file will be overwritten when running amplify pull. *
 **************************************************************************/

/* eslint-disable */
import * as React from "react";
import { Home } from "../models";
import { SortDirection } from "@aws-amplify/datastore";
import { getOverrideProps, useDataStoreBinding } from "./utils";
import HomeCard from "./HomeCard";
import { Collection } from "@aws-amplify/ui-react";
export default function HomeCardCollection(props) {
  const { items: itemsProp, overrideItems, overrides, ...rest } = props;
  const itemsPagination = {
    sort: (s) => s.createdAt(SortDirection.DESCENDING),
  };
  const [items, setItems] = React.useState(undefined);
  const itemsDataStore = useDataStoreBinding({
    type: "collection",
    model: Home,
    pagination: itemsPagination,
  }).items;
  React.useEffect(() => {
    if (itemsProp !== undefined) {
      setItems(itemsProp);
      return;
    }
    setItems(itemsDataStore);
  }, [itemsProp, itemsDataStore]);
  return (
    <Collection
      type="grid"
      searchPlaceholder="Search..."
      templateColumns="1fr 1fr 1fr"
      autoFlow="row"
      alignItems="stretch"
      justifyContent="stretch"
      items={items || []}
      {...getOverrideProps(overrides, "HomeCardCollection")}
      {...rest}
    >
      {(item, index) => (
        <HomeCard
          home={item}
          key={item.id}
          {...(overrideItems && overrideItems({ item, index }))}
        ></HomeCard>
      )}
    </Collection>
  );
}

```