🔖

Vite + React + TypeScript に Vite 用 Storybook を導入する。Storybookは必要だぞ☆

2022/05/23に公開

はじめに

ごめんなさい。懺悔します。
ストーリーブック使ったことがないし、メンバーも少ないし、他に必要なライブラリがいっぱいあって、その使い方とかも調べないといけないし、そろそろ開発も進めないと進捗もやばいし、
正直ストーリーブックまで用意しなくてもよくね?と思ってました。
ぶっちゃけもうメンドウだし、別になくても開発に困らんでしょと。
だから必要なライブラリは大方いれたし、ストーリーブックなしで開発進めちゃいました。

そしたらすぐ気付いたんです。こまごました汎用的なコンポーネント作りだしたらね、
あれ?もしかしてこれってストーリーブックないほうがメンドイんじゃない!?
自分が間違ってました。もう本当ごめんなさい。

というわけで、今回は Vite で作成した React アプリケーションに Vite 用の Storybookを導入する方法です。
つまづきポイントは1か所です。

Storybook

Storybook は、UIのカタログを作るツールで、個別のコンポーネント単体の動作・UIの確認ができます。
つまりコンポーネントにどんな機能があるのか?どんな使い方をするのか?といったUIの説明書みたいなものです。
UI仕様書としてメンバーと共有できるのはもちろん、自分は忘れっぽいのでメモ的にも必要。
その上、コンポーネントを作っている最中はテストにも使えるという、絶対に必要というわけではないけど、あると便利な非常にすぐれたツールです。
https://storybook.js.org/

Vite 用には「@storybook/builder-vite」を使用します。
https://github.com/storybookjs/builder-vite

@storybook/builder-vite のインストール

まずは storybook init コマンドを実行することで、ウィザード形式で Storybook に必要なインストールを行ってくれます。

npx storybook init --builder @storybook/builder-vite

storybook をインストールしていいか聞かれるので y を入力します。

Need to install the following packages:
  storybook
Ok to proceed? (y)

eslint-plugin-storybook をインストール していいか聞かれるので eslint を 導入しているなら y を入力します。

eslint-plugin-storybook のインストールは成功したけど、eslint の設定ができなかったとエラーになります。
サポートしている eslint の設定ファイルは .eslintrc.js か .eslintrc.cjs だけです。
eslint は、あとで自分で設定することにします。

インストールは以上で終了です。

インストールで追加されたファイル等

package.jsonを確認すると、以下のものが devDependencies で インストールされています。

package.json
"devDependencies": {
    @babel/core: v7.18.0,
    @storybook/addon-actions: v6.5.3,
    @storybook/addon-essentials: v6.5.3,
    @storybook/addon-interactions: v6.5.3,
    @storybook/addon-links: v6.5.3,
    @storybook/builder-vite: v0.1.35,
    @storybook/react: v6.5.3,
    @storybook/testing-library: v0.0.11,
    babel-loader: v8.2.5,
    eslint-plugin-storybook: v0.5.12,
    ・・・

scripts にも storybookを起動するスクリプトが追加されています。

package.json
"scripts": {
    ・・・
    "storybook": "start-storybook -p 6006",
    "build-storybook": "build-storybook"

他にも root 直下に .storybook フォルダができています。

src直下には stories フォルダができています。

動作確認

とりあえず以下のコマンドで storybook を実行してみます。

npm run storybook

storybook が起動できます。

eslint の設定

つぎは.eslintrc ファイルを設定します。
storybook の eslint 設定は ストーリファイルだけに適用させたいので、overrides で設定します。

  • eslint-plugin-storybook の オススメ recommended 設定を *.stories.@(ts|tsx|js) にマッチするファイル(=ストーリーファイル)にのみ適用させています。
.eslintrc.yml(抜粋)
・・・
extends:
  - plugin:react/recommended
  - plugin:react-hooks/recommended
  - airbnb
  - airbnb-typescript
  - prettier
overrides:
  - files:
      - '**/__tests__/**/*.+(ts|tsx|js)'
      - '**/?(*.)+(spec|test).+(ts|tsx|js)'
    extends:
      - plugin:jest-dom/recommended
      - plugin:testing-library/react
+  - files:
+      - '*.stories.@(ts|tsx|js)'
+    extends:
+      - plugin:storybook/recommended
ignorePatterns:
  - vite.config.ts
  - vitest.setup.ts
settings:
  import/resolver:
    typescript: []
rules:
  ・・・

次に src 直下の stories フォルダにある ストーリーファイルをみてみます。

  • devDependencies でインストールされた @storybook/react を import している。
    ストーリーファイルは devDependencies でインストールされているライブラリをインポートする必要があるので、.eslintrc で許可するようルールを設定します。

  • デフォルトエクスポートしている。
    私は通常のファイルはデフォルトエクスポートを禁止しているので、ストーリーファイルはデフォルトエクスポートを許可するようルールを設定します。

上記の2点は、各自の .eslintrc ファイルの設定によりますので、必要な場合のみルールを追記します。

src/stories/Button.stories.tsx(抜粋)
import React from 'react'
+ // devDependencies でインストールされた @storybook/react を import している
+ import { ComponentStory, ComponentMeta } from '@storybook/react'

import { Button } from './Button'

// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
+ // デフォルトエクスポートしている
+ export default {
  title: 'Example/Button',
  component: Button,
  // More on argTypes: https://storybook.js.org/docs/react/api/argtypes
  // argTypes: {
  //   backgroundColor: { control: 'color' },
  // },
} as ComponentMeta<typeof Button>

.eslintrc ファイルにルールを追記します。

.eslintrc.yml(抜粋)
・・・
extends:
  - plugin:react/recommended
  - plugin:react-hooks/recommended
  - airbnb
  - airbnb-typescript
  - prettier
overrides:
  - files:
      - '**/__tests__/**/*.+(ts|tsx|js)'
      - '**/?(*.)+(spec|test).+(ts|tsx|js)'
    extends:
      - plugin:jest-dom/recommended
      - plugin:testing-library/react
+  - files:
+      - '*.stories.@(ts|tsx|js)'
+    extends:
+      - plugin:storybook/recommended
+    rules:
+      # default export を許可する
+      import/no-default-export: off
+      # devDependencies からの import を許可する
+      import/no-extraneous-dependencies: off
ignorePatterns:
  - vite.config.ts
  - vitest.setup.ts
settings:
  import/resolver:
    typescript: []
rules:
  ・・・

以上で eslint の設定は終了です。
eslint を実行してコードをチェックしてみましょう。
各自の eslint の設定によって、stories フォルダ内のコードでエラーが出ると思いますので、修正します。

storybook 起動ファイル .main.js の設定

つづいて root 直下の .storybook フォルダにある main.js を設定します。
この main.js が storybook 起動ファイルになります。

Storybook のビルダーはデフォルトでは vite.config.ts ファイルを読み取りません。
main.js で vite.config.ts を読み込むよう設定します。

ここがつまづきポイントです。
公式サイトに vite.config.ts を読み込む方法が記載されていますが、記載されている方法では vite.config.ts ファイル を読み込めませんでした。

こちらに記載されている方法で vite.config.ts ファイル を読み込みます。
参考:https://github.com/storybookjs/builder-vite/issues/85

  1. javascript ファイルだとエラーがわかりにくいので、 main.js の拡張子を main.ts に変更して Typescript ファイルにします。
  2. main.ts を以下のように変更します。
.storybook/main.ts
+ const path = require("path");
+ const { loadConfigFromFile, mergeConfig } = require('vite');

module.exports = {
  "stories": [
    "../src/**/*.stories.mdx",
    "../src/**/*.stories.@(js|jsx|ts|tsx)"
  ],
  "addons": [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-interactions"
  ],
  "framework": "@storybook/react",
  "core": {
    "builder": "@storybook/builder-vite"
  },
  "features": {
    "storyStoreV7": true
  },
+  async viteFinal(config, { configType }) {
+     const { config: userConfig } = await loadConfigFromFile(
+       path.resolve(__dirname, "../vite.config.ts")
+     );
+ 
+    return mergeConfig(config, {
+      ...userConfig,
+      // manually specify plugins to avoid conflict
+      plugins: [],
+    });
+  },
};

動作確認

vite.config.ts ファイルが読み込めているか動作確認のために、自作コンポーネントをストーリブックで起動できるか確認してみます。

私は src 直下のファイルを "@"で始まる絶対パスで import できるよう vite.config.ts でエイリアスパスを設定しています。

ストーリーファイルで、自作コンポーネントを"@"で始まる絶対パスで import できればvite.config.ts ファイルが読み込めていると判断できます。

エイリアスパス設定方法はコチラ
Vite+React+TypeScript+EsLintで、Importパスにエイリアスを使うためにハマったこと

まずは src 直下に mycomponent/MyButton.tsx コンポーネントを作成します。
下記のサンプルは MaterialUi を使用していますが、内容はなんでもいいです。

src/mycomponent/MyButton.tsx
import { Button } from '@mui/material'

export type MyButtonProps = {
  text: string
}

export const MyButton = ({ text }: MyButtonProps) => {
  return (
    <Button variant="contained" size="small" color="warning">
      {text}
    </Button>
  )
}

ストーリーファイルを作成します。
先ほど作成した MyButton コンポーネントを"@"で始まる絶対パスで import します。

src/stories/MyButton.stories.tsx
import React from 'react'
import { ComponentStory, ComponentMeta } from '@storybook/react'

// "@"で始まる絶対パスで import
import { MyButton } from '@/mycomponent/MyButton'


export default {
  title: 'MyComponent/MyButton',
  component: MyButton,
  argTypes: {
    text: { 
      description: 'ボタンに表示する文言を設定します。' 
    },
  },
} as ComponentMeta<typeof MyButton>

const Template: ComponentStory<typeof MyButton> = (args) => <MyButton {...args} />

export const Default = Template.bind({})
Default.args = {
  text: "MyButton",
}

ストーリーブックを実行します。

npm run storybook

表示できました。

以上で、Vite で作成した React アプリケーションに、ストーリブックを導入できました。

まとめ

Storybookは必要だぞ☆

Discussion