React+ViteプロジェクトにStorybook(v7)を導入する

2023.04.25

吉川@広島です。

フロントエンドはもっぱらReact+Viteで開発することが多いのですが、以前これにStorybook v6を導入しようとして挫折したことがありました。Viteに対応させたくbuilder-viteを使ったものの動作させることができず、しばらくViteファーストなStorybook AlternativeであるLadleユーザになっていました。

ところが最近になり「Storybook v7でそのあたり良くなってるよ!」という情報を各所で見聞きするようになりました。ということでReact+Vite+Storybookを試してみたいと思います。

環境

  • react: 18.2.0
  • react-dom: 18.2.0
  • vite: 4.2.0
  • typescript: 5.0.2
  • @storybook/react: 7.0.6
  • @storybook/react-vite: 7.0.7
  • tailwindcss: 3.3.1
  • postcss: 8.4.21
  • clsx: 1.2.1

Reactプロジェクト構築

Vite CLIで新規プロジェクトをinitします。

はじめに | Vite

npm create vite@latest sample-pj -- --template react-ts
cd sample-pj

以降、sample-pjをworking directoryとします。

Storybookを導入

Viteビルド設定でStorybookを導入します。Storybook CLIのinitを叩きます。

npx sb init --builder=vite

そのままだと動かない点があったので、いくらか手を加えてそれぞれ以下のように設定しました。

// package.json
{
  "name": "sample-pj",
  "version": "0.0.0",
  "private": true,
  "type": "module",
  "scripts": {
    "story:dev": "storybook dev -p 9009",
  },
  "dependencies": {
    "react": "18.2.0",
    "react-dom": "18.2.0",
  },
  "devDependencies": {
    "@types/react": "18.0.28",
    "@types/react-dom": "18.0.11",
    "@storybook/react": "7.0.6",
    "@storybook/react-vite": "7.0.7",
    "typescript": "5.0.2",
    "vite": "4.2.0"
  }
}
// .storybook/main.ts
import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = {
  stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"],
  addons: [],
  framework: {
    name: "@storybook/react-vite",
    options: {},
  },
  docs: {
    autodocs: "tag",
  },
};
export default config;

設定は以上です。

サンプルコンポーネントを作り、Storybookで描画してみる

では、次のようなInputコンポーネントを作ってみます。Tailwindでスタイリングしています。

// src/components/Input.tsx
import clsx from "clsx";
import { type ComponentProps, type FC } from "react";

interface InputProps extends ComponentProps<"input"> {}

export const Input: FC<InputProps> = ({ className, ...props }) => (
  <input
    {...props}
    className={clsx(
      "w-full appearance-none rounded border px-3 py-2 leading-tight text-gray-700 shadow focus:outline-none",
      className
    )}
  />
);

これのストーリーファイルを作ります。

// src/components/Input.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import { Input } from "./Input";

const meta: Meta<typeof Input> = {
  title: "Input",
  component: Input,
};

// eslint-disable-next-line import/no-default-export
export default meta;

type Story = StoryObj<typeof Input>;

export const DefaultInput: Story = {
  render: () => <Input />,
};

export const TypedInput: Story = {
  render: () => <Input value="てすと" />,
};

Storybookのローカルサーバを起動します。

npm run story:dev

http://localhost:9009 にアクセスすると、Storybook上でInputコンポーネントを確認することができました。

というわけでStorybook+Viteを動かすことができました。

ハマった点

Storybook init直後の状態だと npm run story:dev 時に以下のエラーが発生しました。

[ERROR] No matching export in "global-externals:@storybook/core-events" for import "DOCS_PREPARED"

一旦 main.ts から addons の内容を除去することで解消しました。

// .storybook/main.ts
  addons: [
    "@storybook/addon-links", // 除去
    "@storybook/addon-essentials", // 除去
    "@storybook/addon-interactions", // 除去
  ]

これについては今後アドオンが必要になったタイミングで再度トライしてみようと思います。

参考