はじめに
Metaps Advent Calendar 第25日目の記事です。
(弊社の場合、Advent Calendarの投稿日は参加してるエンジニア同士で早い者勝ちで決まるのですが、もたもたしてたら最終日のクリスマス当日枠になってしまいました🎄)
本題に入りますが、Figmaで作成されたデザインからReactコンポーネントを自動生成できれば便利ですよね。
今回は、ずっと気になっていた「Amplify Studio」と「Figma」を同期してReactコンポーネントを自動生成する方法を試してみました。FigmaのデザインからReactコンポーネントに変換してReactアプリに組み込む手順について主に書いていこうと思います。このハンズオンに沿って進めていきます。
事前準備
- Githubアカウント
- AWSアカウント
- Figmaアカウント(無料版でOK)
Amplifyアプリケーションのデプロイ
バックエンドとフロントエンドを1つのGUI開発環境でビルドするために、まずはコチラからGitHubのサンプルリポジトリをフォークして、構成済みのバックエンドが含まれた新しいAmplifyアプリケーションをデプロイします。
Githubに接続したら以下のような画面が表示されるので、好きな名前をつけていきます。
今回はamplify-homes
というアプリ名で生成しました。
ここでサービスロールを作成する必要があるので、「新しいロールを作成」を押してIAMからロールを作成していきます。
ロール作成ができたら、そのロールを選択して「保存してデプロイ」をします。
この画面にたどり着くのに5分以上はかかるので、次の手順などを読みながら待つのが良さそうです。
デプロイが完了したら、Backend environments
タブに移動して、「Studio を起動する」を押します。
Amplify Studio Consoleでシードデータの作成
別タブで以下のAmplify Studioコンソールが起動します。ここからFigmaからインポートしたUI Libraryの設定やシードデータの生成などが操作できます。
早速、サイドメニューのData
からData Modeling
の画面をひらいてシードデータを作成していきます。今回は、ID
, address
, image_url
, price
のフィールドを持つデータを定義していきます。
Actions > Auto-generate dataからデータを作成すると、データをいくつ作成するかや、数字の範囲、値の設定などが選択できます。
address
フィールドには住所を入れたいので、「Street address」を設定します。
image_url
はUnsplash's random photo generatorを使いましょうとのことなので、それに従って生成された画像のURLを入力していきます。
そのほか、データ自動生成時に何かしらの制約を追加したい場合は Add constrain
から追加が可能です。
Generate data
を押してデータの生成が完了!!
ここで生成したデータをUI構築時に使用できるようになります。
Figma でデザインテンプレートをインポート
自分でデザインを生成しても問題ないですが、せっかくなのでAWS Amplify UI Kitテンプレートを使ってみることにします。
このUI Kit でFigma を開いて、まずはREADMEに目を通してみます。
Getting Startedの章でPrimitives
の中身は触るな!!と言われているので、このpageは眺めるだけにしましょう。
ここはAmplify UIでpre-buildされたprimitivesが含まれている箇所なので、このページでデザインに変更を加えたとしても、Amplify Studioに反映されないと言われてますね。(いじくる前にREADME見ていて良かった..)
自分でデザインを作成する場合は、My Components
page なら大丈夫そうですね!
primitives page を少し覗いてみると、色んなボタンはもちろんSearchField
とかSliderField
なども用意されてました。
Figma でデザインを作成
Figma でデザインの作成をしたことないので、見よう見真似でチュートリアルで紹介されていたデザインを作ってみることにします。 READMEでコンポーネントを作成するにはYOU HAVE TO USE FRAMES
と記載されているので、frameをまずは作成していきます。
※ Figma上で自分のアカウントの権限がRead Only
になっていてFrame作成のボタンが表示されなかったので注意
HomeCard
という名前でコンポーネント化し、その中にさらにframe
を入れて、Title
とDescription
を作成していきます。
Figmaの基本的な操作方法を調べながらできたデザインがコチラ。
Figma と Amplify Studio を同期
Figmaで作成したコンポーネントをAmplify Studioに取り込むために同期の設定をしていきます。まずはFigmaの右上のShare
ボタンを押して、URLをコピーします。
Amplify Studio に戻り、サイドメニューのUI Library
から右上のSync with Figma
を押し、コピーしたFigmaのURLをAmplify Studio に貼り付けて同期させます。
コピーしたURLを貼り付けると、AmplifyがFigmaへのアクセス権を求めるウィンドウが立ち上がるので、許可をします。
Amplify Studio のUI Library
にFigmaのデザインがComponentsとして作成されていれば成功です!自作したHomeCard
コンポーネントもAmplify Studioに取り込まれてるのが下記の画像から確認できますね。
UI コンポーネントをデータと紐づける
UI Library
にFigmaでインポートしたデザインがComponents
として表示されるので、今回自分で作成したHomeCard
コンポーネントを選択してConfigure
を押します。
ここで開いたUIコンポーネントエディターで、コンポーネントのプロパティと、それに紐づくコンポーネントのUI要素を紐づけていきます。
Component properties
にはAdd prop
から入力フィールドを追加し、Data Modeling画面で作成したHome
をType
に指定します。 Child properties
にはUIコンポーネントの各要素に紐付けたいデータを指定していきます。
Elements tree
で操作したい要素を選択して、どのデータを紐づけるかを直感的に操作することができました。
今回は、要素のTitle
にはhome.address
、Description
にはhome.price
を文字列の中で変数を展開して表示するように割り当てます。 また、onClick action
も割り当てができるので、画像を押したら画像URLに遷移するような動作も実現することができそうです。
これでコンポーネントにデータの紐付けは完了です!
Collectionのコンポーネントを作成する
実際にUIを構築するときには、このコンポーネントを利用して複数のHomeCard
を表示させたいので、コレクションを作成してみます。上記キャプチャの右上のCreate collection
からコレクション名を入力して生成されたコンポーネントが以下になります。 これでシードデータを使用したHomeCardCollection
コンポーネントの作成ができました。画面左側のLayout
で確認できますが、Grid
表示でのカラム数の指定、X-aligin, Y-align やHomeCard
を並べるときの余白もpx
で指定することが可能です。
Reactアプリにコンポーネントを取り込む
上記のComponent Editor 画面で</>Get component code
を押すと、Reactアプリに取り込む手順が表示されるので、それに従えばコンポーネントを取り込むことができます。
今回はReactアプリをまだ作成してないので、以下のコマンドを実行してamplify-homes
という名前でReactアプリを作成します。
npx create-react-app amplify-homes
次にnpmコマンドを実行します。
npm install -g @aws-amplify/cli
npm install aws-amplify @aws-amplify/ui-react
aws-amplify/ui-react
は、AWS AmplifyのReact用UIコンポーネントライブラリです。このライブラリには、認証コンポーネント(ログイン、サインアップフォームなど)や、様々なUI要素が含まれており、Reactアプリケーションでの使用に最適化されているようです。
次に以下のコマンドを実行します。
amplify pull --appId {YOUR_APP_ID} --envName devh
実行すると、Amplifyにログインされるかを聞かれ、その後、FigmaのコンポーネントをReactのコンポーネントに変換するためにいくつか質問が表示されるので、回答していきます。
すると、コンポーネントが自動生成されました!!
amplify-homes
のプロジェクトを確認すると、amplify
, /src/ui-components
ディレクトリが追加されてます。そして、ui-components
の配下には、Amplify Studio から実際にPullしてきたコンポーネントの一覧が格納されてます。Amplify Studio のUI Library > Components
に存在するコンポーネント名と一致するファイルが生成されてることが確認できます。フロント実装時には、ここからコンポーネントをインポートして使用する形になります。
次に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 の「Flex」コンポーネントで使用できるプロパティを使用できるようです。 この例では試しに、NavBarHeader2
にwidth={"100vw"}
を指定してみました。
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
の数などを指定することが可能です。
<HomeCardCollection isPaginated itemsPerPage={3}/>
アプリ起動!!
npm start
FigmaでデザインしたものにAmplify Studio で生成したデータが紐づいた形でUIが構築できてますね! イイ感じです!
まとめ
今回、特に詰まった点はなく、GUIからぽちぽちするだけで、Reactコンポーネントを自動生成できる点は非常に便利と感じました。デザインからコンポーネントを作る作業が爆速になりそうな予感がしました!!ただ実際にFigmaでデザインしてReactの自動生成をしてみた結果、Figmaの知識が少し必要かも、、という所感です。プロジェクトに導入するなら、Figmaでデザイン生成時点でデザイナーと開発者の間でしっかり決め事を決めておかないと想定外のことも起きそうな気がしました。ネックになりそうなポイントを洗い出して検討する必要はありそうかもという感じですね。
最後に出力されたコードを貼り付けて貼り付けておわりにしたいと思います。
Figmaから自動生成されたコンポーネントの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>
);
}
/***************************************************************************
* 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>
);
}