😽

Storybookを導入する際にやるべきこと3選

2022/10/03に公開

TL; DR

以下を推奨

はじめに

フロントエンドエンジニアとして活動するとおそらく耳にするであろうStorybook。
その有用性が語られることもあれば、時に悪名も耳にすることもあるかと思います。
Takepepeさんが以下のような"あえてStorybookを使わない理由はなんですか"というアンケートを取られておりました。
https://twitter.com/takepepe/status/1572943679605833728?s=20&t=jXgJtkUSmJtyodtp48M-JA
結果は、「面倒・メンテナンスコストが高い」でした。
実際に私も先輩に、メンテナンスコストが想像より辛く、いいものではなかったと言われたことがありました。
ですが、私は担当したプロジェクトでStorybookの導入に踏み切りました。
それは、これから紹介する3つの事柄を実践することでStorybookで得られる恩恵が、Storybookのコストを上回ると考えたからです。

Storybookの意義と恩恵

Storybookを導入する際にやるべき事柄を紹介する前に、私が考えるStorybookの意義と恩恵を整理したいと思います。
結論から言うと、Storybookはコンポーネント単位でスタイルを一目で、何度でも確認できるカタログを作れることです。
これはどういうことかという前に、アプリケーション開発における(悲しき)現実に目を向けましょう。
それは動かないコード、意図しない動作をするコードに価値はないことです。
これは恐らく疑いようのない事実かと思います。
一方でバグのないアプリケーションは存在しないことも疑いようのない事実です。
このある種矛盾した現象に直面した我々は次の指導原理を産んだのではないかと思われます。
コードは、1秒でもはやく、リアルタイムに、一目で、何度でも、独立した状態で、コードの振る舞いを確認できるようにすること
即ち高速で、変更に耐え、理解が容易で、安定したフィードバックループを形成すること、これはあらゆるところで目にする指導原理かと思われます。
さてモダンフロントエンドフレームワークはコンポーネント指向と呼ばれる道を進みました。
コンポーネントという独立した対象を扱えるようになったおかげでフロントエンドの自動テストは躍進しました。
つまりコンポーネントの持つ振る舞いを高速に、何度でも、独立した状態で確認できるようになったのです。
ところでコンポーネントが持つ最も重要な構成要素の1つにスタイル、端的に言えばHTML/CSSの振る舞いがあります。
ただコンポーネントのスタイルを確認する作業は意外と大変です。
それは、スタイルの確認はブラウザを介する必要があるからです。
実際、Next.jsを利用し、何もサポートがない場合、適当なページにコンポーネントを配置し、devサーバーを立ち上げ、ブラウザで確認するといったことが必要です。
しかしブラウザに表示するために、ページにコンポーネントを配置する類の作業は些か面倒で、即時性に欠けます。
またコンポーネント毎にページを作るわけにはいかないので、独立性にも反しています。
これらは明らかに指導原理に反しています。
コンポーネントはコードに書き起こしたら即座に、かつ独立した状態で振る舞いを確認できるべきなのです。
これはスタイルも例外ではないと考えています。
前置きが長くなりましたが、これこそがStorybookの意義であり、恩恵です。
つまりStorybookはコンポーネントのスタイルという観点で、リアルタイムに、何度でも、独立した状態で振る舞いを確認できる場を提供すること、これが意義と恩恵です。

3大Storybook導入においてやるべきこと

ですが、Storybookはメンテナビリティに優れているかというと決してそうではないというのも事実です。
(これはHTML/CSSが複雑なセマンティクスを内包することに起因しているのではないかと睨んでいます)
私は以下の3要点を抑えることで、Storybookの恩恵がコストを上回ると考えました。
順次紹介していきます。

Storybookのためにディレクトリ構造を作らないこと

最初に紹介するのは、Storybookのためにディレクトリ構造を作らないことです。
いきなり紹介することが、やらないことで驚きかもしれないですが私は至って大真面目です。
Storybook専用のディレクトリ構造を作ってはいけません。
私が推奨するのは、コンポーネントのファイルの隣に配置することです。
例えばButtonコンポーネントは、以下のように構成します。

  • Button
    • index.tsx
    • Button.spec.tsx
    • Button.stories.tsx
    • styles.module.css

これを推奨する理由は、Storybookのディレクトリを作ると、コンポーネントディレクトリとの2重管理となるからです。
これはStorybookがコンポーネントのスタイルを確認するカタログであることを考えると、自然とコンポーネントディレクトリの構造に追従します。
追従するぐらいなら最初から隣に配置することで、ディレクトリ構造というメンテナンスの障害を取り除いておくということがベターだと考えています。
(これはテストファイルにも言えると考えています。私はテストファイルもコンポーネントの隣に配置します)

Storybookは99%自動生成させること

次に推奨することは、Storybookを99%自動生成させることです。
これはコンポーネント作成手順をテンプレート化し、Storybookに必要な作業の99%を済ませてしまう状態にすることです。
この説明では具体性に欠け、何をすべきかの配慮に欠けていますので、順次説明します。
まずStorybookの実際のコードを見ていきましょう。

import { type Meta, type Obj } from "@storybook/react"
import { Button } from "."

type T = typeof Button

export default {
  component: Button,
  args: {
    children: '送信',
  },
} satisfies Meta<T>;

export const Default: StoryObj<T> = {}
バージョン6の記法
import { type ComponentMeta, type ComponentStoryObj } from "@storybook/react"
import { Button } from "."

type T = typeof Button
type Meta = ComponentMeta<T>;
type Story = ComponentStoryObj<T>;

export default {
  component: Button,
  args: {
    children: '送信',
  },
} as Meta

export const Default: Story = {}

さて上記のコードで私が実際に手を動かして編集したのは、以下の箇所のみです。

export default {
  component: Button,
  args: {
+    children: '送信',
  },
} satisfies Meta<T>;

他の大部分はコードジェネレーターで生成しています。
(コードジェネレーターの例としてPlopがあります)

https://plopjs.com/

これのカラクリを説明するに、Storybookのコードのうち、コンポーネント固有のものは以下の2つです。

  • Buttonというコンポーネント名
  • argsのchildrenプロパティ

argsは実装の詳細のため、共通化は厳しそうですがコンポーネント名は単なる代入に過ぎません。
コードジェネレーターはこの代入作業をコマンドライン上で行えます。
このためStorybookの作成は、argsの修正程度で済むことが多いです。

テンプレートのサンプルコードをGitHubにあげております。

https://github.com/mymactive/my-code-generator-templates

Storybookからスナップショットを自動生成させること

最後に紹介するのは、Storybookからスナップショットを自動生成させることです。
スナップショットテストは、フロントエンドにおいて地位を確立しているテスト手法です。
ここではスナップショットテストの意義については割愛しますが、気になる方はJestのスナップショットテストをご一読ください。

https://jestjs.io/ja/docs/snapshot-testing

Jestにおけるスナップショットテストの冒頭を引用すると、次のようなことが書いてあります。

スナップショットのテストはUI が予期せず変更されていないかを確かめるのに非常に有用なツールです。

読み取るにスナップショットテストはUI、即ちスタイルを主な対象としたテストです。
ここでStorybookの意義と恩恵を思い出してください。
Storybookはコンポーネントのスタイルを対象としたものです。
これらに相互補完の関係があるのではないか考えるのは自然なことであると思われます。
実際にそうです。
スナップショットテストがコンポーネントの変更を検知した際に、Storybookが提供することでカタログでスタイルの何が変更されたかを確認できます。
これはまさしく相互補完の関係です。
更にStorybookからスナップショットは自動生成できる仕組みが提供されています。
例えばStorybookのアドオンとしてStoryshotsがあります。

https://storybook.js.org/addons/@storybook/addon-storyshots

このような仕組みをStorybookと合わせて導入することで、より大きなリターンを得られます。
(テスト工数のコスト削減という切り口で捉えてもいいかもしれません)

追記

この節は些か乱暴な議論を振り回しており、それに対してご指摘がありました。

https://twitter.com/takepepe/status/1576698040769187840?s=20&t=G-1AAiSA-fO-rUWhDUfV3A

端的に言えばスタイルは、上記の意味でのスナップショットテストでカバーできる範疇ではないということであります。
これは実際正しく、スナップショットテストではインラインスタイルではないCSS手法を採用した際はCSSはモックされるためスタイルの変更検知とまではいきません。
ですので正確には、スナップショットテストはStorybookと正の相互作用があるという程度に留めるのがベターでしょう。
スタイルまでカバーするにはVisual Regression Testの検討を推奨します。
(ご指摘ありがとうございました。この場を借りてお礼申し上げます)

まとめ

紹介した3要素は一言で言えば、次のようにまとめられます。
プロジェクトの複雑化を避け、ファイル生成のコストを共通化で抑え、スナップショットテストと絡めてリターンを得る。
またStorybookはコンポーネント指向におけるスタイルに関して強固なフィードバックループを作るという点で、アプリケーション開発の指導原理に沿っていると私は考えています。
無論Storybookは時に、スタイルというメンテナビリティの低い対象と向き合うため牙を剥くかと思います。
実際私も、困ったことに何度も直面しましたし、今後も困ることがあると思います。
ですが、Storybookがスタイルという魔物に真正面から向き合う姿勢は尊敬されるべきものであると思います。

おまけ

以下は蛇足ですが、検討すると楽しげなものを紹介します。

Component Story Format 3.0で記述する

Storybookの記述は幾度かバージョンアップし、執筆当時ではバージョン3まで出ています。
ガイドやチュートリアルではバージョン2の記法がありますが、個人的に現状はバージョン3が最も自然な書き方に見えます。
下記のガイドを是非一読し、バージョン3スタイルでStorybookを記述することを推奨します。
https://storybook.js.org/blog/component-story-format-3-0/

Pure View Componentを作る

こちらはフロントエンドの"ちょうどいい"自動テストのはじめかたの「"Pure" View Componentを作っておく」にて紹介された手法です。
実はStorybookはロジック面と相性が悪く、適宜モックが必要となります。
実際私はRecoilを呼び出すコンポーネントを実装時にStorybookが壊れてしまいました。
ですが、Storybookの主眼はあくまでスタイルです。
スタイルだけを持った、ロジックを持たないコンポーネントを作ってしまえば良いのではないかというアイデアがPure View Componentです。
(私はこの考え方に触れた時まさに、目から鱗でした)
他にも面白く、興味深いフロントエンドに関わるテスト指針が記載されております。

https://atraetech.hatenablog.com/entry/2022/09/30/105747

Storybookを自動テストに活用する

Storybookはコンポーネントのスタイルにおいて以下の指導原理に沿ったものであると述べました。
コードは、1秒でもはやく、リアルタイムに、一目で、何度でも、独立した状態で、コードの振る舞いを確認できるようにすること
一方でコンポーネントのロジックに関して指導原理にアプローチしたのが、Testing LibraryJestのようなテストライブラリです。
そもそもこの指導原理を最も体現しているのは、自動テストです。
Storybookは自動テストと分類されることがありますが、この指導原理に従っているという意味ではないかと私は考えています。
さて驚くべきことにStorybookは、これらテストライブラリと橋渡しできる仕組みが作られています。
具体的な例については、以下の記事を読み、記事内で紹介されているGitHubレポジトリを辿ることを推奨します。
(現代フロントエンドの自動テストに必要なエッセンスが明快にまとまっており、サンプルコードも充実した日本語における屈指の記事です)

https://zenn.dev/takepepe/articles/nextjs-testing-strategy-2022

Visual Regression Testを試みる

非インラインスタイルのスタイリングを採用する場合は、CSSがモックされるため純粋にCSSのスタイリングという観点でスナップショットテストは物足りません。
残念ながら私がまだここまで実践できておりません。
というより私の目下の課題はVisual Regression Test(VRT)の検証です。
導入後の肌感、運用の課題、そして妙案、何でもフィードバックがほしいです。
Storybookを利用したVRTの具体的な記事を紹介します。
VRTの記事は貴重なため、これが日本コミュニティに存在することは大変ありがたいです。

https://blog.recruit.co.jp/rmp/front-end/visual-regression-testing/

GitHubで編集を提案

Discussion