1日のスケジュール管理Webアプリ「daydule」 -　Storybookの導入

## はじめに
皆さん、こんにちは！
最近韓国料理を食べることにハマっているエンジニアの@atokです。

今回は私たちが作成したスケジュール管理アプリ[daydule](https://frontend.daydule.net/about
)に導入した「Storybook」のインストールや設定手順についてご紹介します。
なお、私たちはStorybookをホスティングとテスティングのSaaSである"Chromatic"と組み合わせて使用しています。

アプリの概要やシステム概要について詳しく知りたい方は、[関連記事](#関連記事)を参照してください。

## プロジェクトの前提

フロントエンド開発では、以下の技術を採用しています。

||技術スタック|
| :-: | :-: |
|     フロントエンド     | TypeScript / React, Next.js |
|        スタイル        |                  <a target="_blank" rel="noopener noreferrer" href='https://tailwindcss.com/' target="_blank">Tailwind CSS</a>                   |
|     バージョン管理     |                                                                       Git                                                                        |
|   リモートリポジトリ   |                                                                      GitHub                                                                      |

※フロントエンドサーバーやインフラなど、Storybookの導入に関連のない内容については記載を省いています。
技術スタックに関する詳細は[こちら](https://qiita.com/tom-takeru/items/edb78d86374fae24354b#%E6%8A%80%E8%A1%93%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%AE%E3%81%8A%E8%A9%B1)の関連記事をご参照ください。

## Storybookの概要
[Storybook](https://storybook.js.org/)は、ユーザーインターフェース（UI）コンポーネントの開発、管理、テストを支援するオープンソースツールです。
Storybook を使用することで、UI コンポーネントをビジネスロジックやコンテキストから切り離して開発できるようになり、アプリを実行せずにUI開発を行うことができます。
また、UIコンポーネントをカタログのように見ることができ、各コンポーネントの詳細情報（どんなバリエーションがあるかなども含め）を容易に確認することができます。


## Chromaticについて
[Chromatic](https://www.chromatic.com/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook)は、Storybookのメンテナーが作成したホスティングサービスです。
Chromaticを使うことで、WEB上でStorybookを確認できるようになります。

Chromaticは基本的に無料で使用できますが、フリープランの場合は、月5,000スナップショットまでという制約があります。

スナップショットは“UI Review” もしくは “UI Tests”を使用する場合にカウントされます。
例えば「50ストーリー」が存在する状態でStorybookがビルドされると、1回のビルドでスナップショットが50カウントされます。
私たちはプルリクエスト作成のタイミングでStorybookのビルドを実施しているため、100PR/月までであれば無料プランで問題なく使用できる計算になります。
私たちのプロジェクトであれば、無料プランで十分に対応できる見込みです。

以降の章ではプロジェクトにStorybookを導入した際に実際に行った対応を記載します。


## 既存プロジェクトへのStorybook導入

### 1. Storybookのインストールと設定

#### 1.1 Stobybookをインストールする
```Bash
npx storybook@latest init
```

#### 1.2 各種種定を行う
##### 1.2.1 tailwindの読み込みを追加する
<code>previes.ts</code>は、全てのstoryに適用される共通的な設定を記述するファイルです。
CSSインポートはここに記述する必要があります。
```diff_tsx:.storybook/preview.ts
  import type { Preview } from "@storybook/react";
+ import "../src/styles/global.css";

  const preview: Preview = {
    parameters: {
    //以下略
```
##### 1.2.2 .gitignoreを設定する
storybookをビルドした際に作成されるフォルダと、ログをgit管理対象外にします。

```.gitignore
# storybook
/storybook-static
build-storybook.log
```

##### 1.2.3 サンプルのStory及びコンポーネントを削除する
Storybookをインストールした時点で、以下のサンプルのStoryが作成されています。
不要なスナップショットの作成を避けるため、これらは削除しておきます。
- Button.stories.ts
- Header.stories.ts
- Page.stories.ts

#### 1.3 テスト用のStoryを作成し動作確認する
任意のコンポーネントに対してStoryを作成し、Storybookで確認します。

##### 1.3.1 任意のコンポーネントに対して、ストーリーを作成する

```tsx:src/components/AmountDisplay.tsx
import React from 'react';

type AmountDisplayProps = {
  /**
   * 指定可能な背景の色のタイプです。
   */
  variant: 'white' | 'gray' | 'dark';
  /**
   * 指定する金額です。
   */
  amount: number;
};

const AmountDisplay = ({ variant, amount }: AmountDisplayProps) => {
  let bgColor;

  switch (variant) {
    case 'white':
      bgColor = 'bg-white';
      break;
    case 'gray':
      bgColor = 'bg-stone-300';
      break;
    case 'dark':
      bgColor = 'bg-stone-600';
      break;
  }
  return (
    <div className={`${bgColor} rounded p-4 shadow-md`}>
      <span className='text-2xl font-semibold'>${amount.toFixed(2)}</span>
    </div>
  );
};

export default AmountDisplay;
```

```tsx:src/components/AmountDisplay.stories.tsx
import { Meta, StoryObj } from '@storybook/react';
import AmountDisplay from './AmountDisplay';

const meta: Meta<typeof AmountDisplay> = {
  component: AmountDisplay,
  title: 'Test/AmountDisplay',
  argTypes: {
    //以下のようにすると、デフォルトのラジオボタンから選択ボックスに変更できる
    variant: {
      control: { type: 'select' },
      options: ['white', 'gray', 'dark'],
    },
  },
  tags: ['autodocs'],
};

export default meta;

type Story = StoryObj<typeof meta>;

/**
 * 白色のディスプレイです。
 */
export const BaseDisplay: Story = {
  args: {
    variant: 'white',
    amount: 1111,
  },
};

/**
 * グレーのディスプレイです。
 */
export const GrayDisplay: Story = {
  args: {
    variant: 'gray',
    amount: 1111,
  },
};

/**
 * ダークグレーのディスプレイです。
 */
export const DarkDisplay: Story = {
  args: {
    variant: 'dark',
    amount: 1111,
  },
};

export const GroupedDisplay = {
  render: () => (
    <div>
      <AmountDisplay variant='white' amount={1111} />
      <AmountDisplay variant='gray' amount={1111} />
      <AmountDisplay variant='dark' amount={1111} />
    </div>
  ),
};
```

##### 1.3.2 Storybookを確認する

- 以下コマンドを実行し、storybook-staticディレクトリに静的サイトとしてStorybookを出力させます。
```Bash:
npm run build-storybook
```

- 続けて以下コマンドを実行し、http://localhost:6006/ にサーバーを立ち上げます。

```Bash:
npm run storybook
```

以下のように作成したストーリーが表示されていれば問題ないです。

![Screenshot 2023-10-14 at 11.07.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150636/6be30889-3359-a4a3-6fab-a6123ade9550.png)

以降の手順で、このStorybookをChromaticにホスティングしていきます。

### 2. Chromaticのインストールと設定
画面操作についての詳細は[公式チュートリアル](https://storybook.js.org/tutorials/intro-to-storybook/react/en/deploy/#:~:text=u%20origin%20main-,Get%20Chromatic,-Add%20the%20package)をご参照ください。
※日本語版のドキュメントはメンテナンスされていないので、英語版を参照することをお勧めします。

#### 2.1 Chromaticをインストールする

```Bash:
npm install --save-dev chromatic
```

#### 2.2 project-tokenを取得する

インストールが完了したら、以下の手順でproject-tokenを取得します。

- [Chromatic](https://www.chromatic.com/start/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook)にGithubアカウントでログインする。
- ログイン後に「Choose from GitHub」をクリックし、リポジトリーを選択する。
- 遷移先画面（以下画像）のproject-tokenをコピーする。
![Screenshot 2023-10-14 at 11.14.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150636/0754797f-51ba-112b-705a-ed6fe9c0971c.png)

- ここで取得したproject-tokenを.envファイルなどに設定します。
```.env
CHROMATIC_PROJECT_TOKEN = <your-project-token>
```

- こちらも忘れずにgit管理対象外とします。
```.gitignore
.env
```


### 3. Storybookのデプロイ

#### 3.1 chromaticコマンドの実行をpackage.jsonに記述する

```diff_jsonnet:package.json
{
  "scripts": {
//中略
     "storybook": "storybook dev -p 6006",
-    "build-storybook": "storybook build"
+    "build-storybook": "storybook build",
+    "chromatic": "npx chromatic --project-token=$CHROMATIC_PROJECT_TOKEN"
  }
}
```

#### 3.2 ChromaticにStorybookをデプロイする

##### 手動デプロイの設定
- 以下コマンドの実行で、StorybookのビルドとChromaticへのデプロイを同時に行えます。
```Bash:
npm run chromatic
```

- 完了時に表示されるURLで、デプロイされたStorybookが確認できます。
![Screenshot 2023-10-14 at 11.37.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150636/1eba27e1-5e56-af51-e975-5f9f57966f5a.png)

##### 自動デプロイ（GitHub Actions）の設定

###### 設定ファイルを作成する
- プルリクエスト作成のタイミングでChromaticにデプロイするように設定を記述します。
　※[公式ドキュメント](https://www.chromatic.com/docs/github-actions/)を参考に、Eventの設定部分のみを修正しています。
```.github/workflows/chromatic.yml
# Workflow name
name: 'Chromatic Publish'

# Event for the workflow
on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]

# List of jobs
jobs:
  test:
    # Operating System
    runs-on: ubuntu-latest
    # Job steps
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - run: npm ci
        #👇 Adds Chromatic as a step in the workflow
      - uses: chromaui/action@v1
        # Options required for Chromatic's GitHub Action
        with:
          #👇 Chromatic projectToken,
          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
          token: ${{ secrets.GITHUB_TOKEN }}
```

###### GithubのSecretにproject-tokenを設定する

- Githubリポジトリで、<code> 「Settings」>　「Secrets and variables」> 「Actions」</code>
の順に画面操作し、「New repository secret」ボタンを押下します。

![Screenshot 2023-10-14 at 15.48.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150636/fb3b7f33-8109-5a5d-38fe-203c15fdddf7.png)

- <code>「CHROMATIC_PROJECT_TOKEN」</code>という名称でproject-tokenを保存します。

![Screenshot 2023-10-14 at 15.57.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150636/2fbbadfa-6e9e-5c5e-51e6-f89a6ac3eade.png)


上記設定により、開発者がプルリクエストを作成したタイミングで、StorybookがChromaticに自動でデプロイされます。


### 4. Chromaticの設定

#### 4.1 “UI Review” を有効化する

ChromaticのManageタブを選択し、画面右側のボタンをクリックすることで"UI Review"を有効化できます。
（添付画像は有効化した後の状態）
![Screenshot 2023-10-11 at 8.35.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150636/1cfb3f21-ad28-701f-435b-76f696790d28.png)

#### 4.2 “Visual tests” を有効化する
"Visual Tests"も同様に有効化します。
![Screenshot 2023-10-11 at 8.35.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150636/422a7cf3-26f4-ba0c-cf6d-f304245de7d1.png)


これらの設定をした状態でプルリクエストを作成すると、
・Storybookのビルド
・Chromaticへのデプロイ

が行われ、また以下それぞれのチェックが作成されます。
・UI Tests
・UI Review

![Screenshot 2023-10-14 at 17.27.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/150636/99353485-d9de-77d0-fab4-c5406b49024d.png)

## 最後に

まだまだ運用が始まったばかりではありますが、Storybookの導入に関しては他メンバーからも前向きな意見が多かったです。
これから運用していく中で、得た知見については改めて記事にまとめていきたいと思います。
特に、実際のプロジェクトでのStorybookの効果やベストプラクティス、UIコンポーネントの設計上の洞察などを共有できればと考えています。
それでは！


## 関連記事
・[1日のスケジュール管理Webアプリ「daydule」 - 概要](https://qiita.com/tom-takeru/items/edb78d86374fae24354b)
・[1日のスケジュール管理Webアプリ「daydule」 - システム概要](https://qiita.com/mellbrother/items/1d44828925fc9cbb8e13)