Gatsby&Typescript&StorybookでComponent開発を効率化する

May 20th, 2019

Introduction

ReactやVueで新規のComponentを書くとき、画面で実際に使いながらComponenntを作って、あとになって 「あー、あのときこう作っておけば」 「そもそもこのComponentって何渡せばいいんだっけ?」 「なんか似たようなComponentあるな、、、」 なんて経験、筆者だけじゃないと思います。 Component思考において最も重要な「どう使うか」という観点が、実際に画面をみながら開発するとどうしてもその場しのぎというか、一旦画面要件を満たす形だけ作ってしまってComponentとしての独立性とか責務わけがぐちゃぐちゃになったりしやすかったりしますよね。

What is Storybook

StorybookはこういったComponent開発のやりずらさを解消してくれます。 具体的には

  • 独立したComponentの一覧
  • Componentの表示や振る舞いのデモ
  • 使い方(propsの型定義など)のドキュメント

といった機能を提供してくれるので、よりComponentにフォーカスして実装することができます。 ※ヒーローイメージはこのサイトのStorybookのキャプチャです。

ちなみにStorybookの由来は、Componentが利用される場面=StoryをまとめたものだからStorybookという由来、、、だと思っています(推測です、ごめんなさい)。

導入

ReactアプリケーションにStorybookを入れたい方は、公式に色々まとまってるのでこのブログより先に見たほうがいいです。 Storybook Quick Start Guide

Gatsbyでの導入についてもGatsbyの公式に乗ってるので、一旦そちらを見たほうがいいです。 Gatsby Visual Testing with Storybook

公式にまとまってるし簡単じゃん! ...で終わればいいんですが、このブログではGatsby + Typescriptで構成しているのでちょっと罠があってハマりました。

Gatsby + Typescript + Storybookのwebpack設定

package version

  • gatsby: 2.3.32
  • @storybook/react: 5.0.11

まずはGatsby公式の通りに

sb initコマンド実行して公式の指示通りにwebpack.config.js用意すると普通に動きました。 ...が、実際にComponentを呼ぼうとすると.tsファイルを読み込めない。。。 まぁloaderでTypescriptのloaderかましてないからそりゃそうです。

ということでbabel-loaderにTypescriptの設定をする必要が出てきます。

TypescriptをStorybookのビルド環境でトランスパイル

  • typescript
  • babel-loader

gatsby-plugin-typescriptを利用してるとプラグイン内にトランスパイル設定とかパッケージが隠蔽されるからStorybook側でwebpackでloaderかますには別途npm iしなきゃいけないんですよね。 ということで上記の2つをいれて、webpack.config.jsを以下のように書き換えました。

module.exports = ({ config }) => {
  config.module.rules.push({
    test: /\.(ts|tsx)$/,
    use: [
      {
        loader: require.resolve('babel-loader'),
        options: {
          presets: [['react-app', { flow: false, typescript: true }]],
        },
      },
    ],
  })
  config.resolve.extensions.push('.ts', '.tsx')
  return config
}

これでTypescriptのComponentもStorybookで読み込めるようになりました。

Storybookの豊富なアドオン

Storybookにはアドオンが豊富なんですが、今回は必要なものだけにしようと思い

  • addon-actions
  • addon-info
  • addon-links
  • addon-storysource
  • addon-viewport

らへんのみ入れてみました。 この辺は結構オーソドックスなものなので入れてる人も多いと思います。

ただここでまたTypescriptとの掛け合わせの問題が出てきます。。。 addon-infoで自動解析してくれるはずのpropsの定義ドキュメントが空になる。。。 これはStorybookのwebpackの中でreact-docgen-typescript-loaderをloaderに追加することで解決しました。

module.exports = ({ config }) => {
  config.module.rules.push({
    test: /\.(ts|tsx)$/,
    use: [
      {
        loader: require.resolve('babel-loader'),
        options: {
          presets: [['react-app', { flow: false, typescript: true }]],
        },
      },
      {
        loader: require.resolve('react-docgen-typescript-loader'), //  addon-info用
      },
      {
        loader: require.resolve('@storybook/addon-storysource/loader'), // addon-storysource用
        options: { parser: 'typescript' },
      },
    ],
  })
  config.resolve.extensions.push('.ts', '.tsx')
  return config
}

react-docgen-typescript-loaderによってStorybook側で型定義を解析できるようになり、addon-infoにもprops定義が表示されるようになりました。

Storybookを使ってみて

ここまででGatsby+Typescript+Storybookが実際動くようになり、Storyを描き始めたのですが、StoryでComponentをみてみると謎にinputに白い背景色がついてたり、Prismの初期化を呼び出すComponentがpageレイヤーになってたり、と ん?ってなる箇所が思いの外あって、驚きました。

自分の中では結構綺麗に作ってるつもりだったので、ちょっとショックでしたがこういった気づきがある時点でStorybookでのComponent開発は意味をなしてくるんでしょうね。

addon-infoについて

今回やってみて思ったんですが、Storybookを使うならこれは絶対必須です。 チーム開発においてComponentの使い方や責務の認識共有って非常に難しいと思うんですが、これがあればドキュメントとかかかないでもComponentの振る舞いがわかるから非常に楽。 Typescriptで補完が効いても、他人が作ったComponentを利用するときどうしても不安になって結局Componentのソースみないといけない、、、みたいな状況を緩和してくれる気がします。

感想

Storybook非常にいいですね。 なんかTDD的な、単一責務のモジュラリティ向上を目的としたツール感がすごい。

まだテスト系整えられてないので、今後はテスト系整えていこうと思います。