8️⃣

storybook8をプロダクトに導入して起きた問題と解決策をまとめてみた

2024/07/22に公開

こんにちは、株式会社 Rehab for JAPAN エンジニアのもじゃ(@moja_moja)です。

私の携わっているプロダクトで storybook 8 を導入しました。

私自身、これまで storybook を実務で使用したことがなかったため、導入して色々な問題やエラーに遭遇しました。

今回は、導入時に起きた問題と解決策について紹介していきたいと思います。

これから storybook 8 の導入・検討をする方々の参考になると幸いです。

導入する恩恵について

問題と解決策を紹介する前に、プロダクトに storybook を導入するメリットや恩恵について紹介しようと思います。

様々な記事を参考にして、私がプロダクトに storybook を導入することで得られる恩恵は 「バックエンド・Docker に影響されずにフロントエンドだけで完結した開発が可能になる」 のではないかと考えました。

API に接続しなくても Story コンポーネントに渡す props を自由に設定することで、様々な UI を再現しながら実装することができれば、フロントエンドエンジニアは自身の作業に集中できると考えました。

関わるプロダクトでは解消している部分もありますが BE と結合してデータを作成しないと確認できない UI もあるため、導入することでデータを作成する手間を省けるのではないかと考えました。

また、手動でテストしている部分もまだ存在するため、今後、VRT (Visual Regression Test)としての活用も期待できるので導入を進めました。

基本の書き方

Storybook 7 からComponentStoryComponentStoryObjComponentStoryFnComponentMeta が非推奨の型になり、v8 からは@storybook/reactから import もできなくなりました。

代わりに story を登録する場合はMetaStoryObjStoryFnを使用します。

実装例

import { Meta, StoryObj } from '@storybook/react'

import { Button } from '.'

export default {
  title: 'components/Button',
  component: Button,
} satisfies Meta<typeof Button>

type Story = StoryObj<typeof Button>

export const Default: Story = {}

導入して困った点と解決策

DOCS が表示されない

Storybook 8 以前の version だと起動時に「canvas」「DOCS」とタブを切り替えて DOCS を確認できますが、Storybook 8 からは UI が変わって DOCS がタブの切り替えで表示されなくなりました。

storybook 8 を触る前に 過去の version の storybook を触っていたので、storybook8 を初めて立ち上げた時に UI の変化と DOCS がないことに戸惑いを感じつつ、DOCS を表示させる方法を探しました。

解決策

  • tags: ['autodocs'] を.storybook/preview.ts に記述する

https://storybook.js.org/docs/writing-docs/autodocs

storybook では「Global | Component | Story」の 3 つの レベル設定があります。

  • Global  .storybook/preview.ts
  • Component  export default { ~ }
  • Story  export const Default: Story = { ~ }

個別で設定をしたい場合はComponentStoryに記述することも可能ですが、DOCS は共通で適用したい設定のため、.storybook/preview.ts に記述しました。

story によっては DOCS が必要ない場合はComponentStorytags: ['!autodocs']と記述することで DOCS の非表示することも可能です。

  • .storybook/preview.tsの記述例
import type { Preview } from "@storybook/your-renderer";

const preview: Preview = {
  tags: ["autodocs"],
};

export default preview;
  • story ファイルの記述例
import type { Meta } from "@storybook/your-framework";

import { Button } from "./Button";

const meta: Meta<typeof Button> = {
  component: Button,
  tags: ["autodocs"], // Componentで設定する場合
};
export default meta;

type Story = StoryObj<typeof Button>;

export const Default: Story = {
  tags: ["!autodocs"], // storyでDOCSを適用させない場合
};

なお、DOCS は過去の version と異なり、各 story と同じディレクトリ内に表示されるようになりました。

storybook8のDOCS

args に定義するデータが多い場合、コードが肥大化する

component に props を渡す場合、story ファイルでは args に props に渡すデータを記述する必要があります。

props として渡すデータが多い場合、記述するコード量の肥大化が問題としてありました。

コード量が多いと見づらく、PR の際にレビュワーの負担になるため対応を考えました。

解決策

  • story ファイルと同じ階層にデータを管理するファイルを作成

mock データは story ファイルや component ファイルと同じ階層に作成するコロケーションパターンを採用しました。

fixtures.tsというファイルを作成し、mock データを記述し、story ファイルではインポートを行うようにしました。

  • ディレクトリ構成例
/src
├── components
    ├── Button
        ├── index.tsx
        ├── index.stories.ts
        └── fixtures.ts
  • fixtures.ts 記述例
export const user = {
  id: 1,
  name: "テストユーザー",
  email: "test@sample.com",
  gender: "1",
  birthday: "2000-01-01",
};
  • index.stories.ts 記述例
import { Meta, StoryObj } from '@storybook/react'
import { user } from './fixtures'
import { MyPage } from '.'

export default {
  title: 'MyPage',
  component: MyPage,
  args: {
    ...user,
    notification: false
  },
} satisfies Meta<typeof MyPage>

type Story = StoryObj<typeof MyPage>

export const Default: Story = {}

export const onNotification: Story = {
  args: {
    notification: true
  }
}

この方法を採用することでコード量の軽減やデータの管理もしやすくなったので運用していてかなり楽になりました。

SVG を使用しているコンポーネントを読み込むとエラーになる

関わるプロダクトでは svg を使用しているコンポーネントがあり、 story ファイルを作成して確認すると下記のエラーが出てました。

Failed to execute 'createElement' on 'Document': The tag name provided ('~/~/~.svg') is not a valid name.

解決策

  • storybook を"^8.1.0"から"^8.1.2"にマイナーアップデート
  • .storybook/main.tsに SVG をインポートするコードを追加
  webpackFinal: async (config) => {
    config.module = config.module || {}
    config.module.rules = config.module.rules || []

    config.module.rules.forEach((rule) => {
      if (rule?.['test']?.test('.svg')) {
        rule!['exclude'] = /\.svg$/
      }

      return rule
    })

    config.module.rules.push({
      test: /\.svg$/,
      use: ['@svgr/webpack'],
    })

    return config
  },

chakra ui が反映されない

関わるプロダクトでは UI コンポーネントライブラリとして、chakra ui を採用しています。

storybook で chakra ui を適用する場合は公式では@chakra-ui/storybook-addonする必要があると記載されています。

https://v2.chakra-ui.com/getting-started/with-storybook

公式の手順では.storybook/main.tsfeatures: { emotionAlias: false }を追加すると記述されていますが、導入時の storybook 8 では features 内にemotionAliasが存在していないため、手順通りに進めないことがわかりました。

解決策

  • decoratorsChakraProviderを記述する

こちらについては他の人が同じ問題で issues を立てているのを見つけました。

https://github.com/chakra-ui/chakra-ui/issues/8156

しかし、公式から問題に対しての回答がないため issues に記載されている.storybook/main.jsdecoratorsChakraProviderを記述することで反映されるようになりました。

const decorators = [
  (Story) => (
    <ChakraProvider theme={theme}>
      <Story />
    </ChakraProvider>
  ),
];

終わりに

今回は storybook 8 の導入で起きた問題と解決策について紹介しました。

導入して間もないため、v8 で使用できる機能の導入や運用面は記載段階でまだ決まっていない部分も多く、実装を進めながら検討を進めています。

使用できる機能はなるべく導入はしていく予定ですが、一方で風呂敷を広げすぎても扱えず、形骸化になる懸念もあります。

そのため、まずは基本方針をまとめ、チーム内に周知を行うことや story ファイルを記載する文化の浸透を目指していきたいと考えています。

Rehab Tech Blog

Discussion