blog.ryota-ka.me

Storybookの中から別のStorybookを参照できるStorybook compositionを試してみる

Storybook 6.0 から,あるStorybookの中から別のStorybookを参照することができる"Storybook composition"という機能が導入された.

これは目玉機能として挙げられているものの,2021年1月現在この機能についてのドキュメンテーションが十分になされていない.そのため,不足している情報を補完することを目的としてこの記事を書くことにした.

モチベーション#

HERPではherpismと呼ばれる社内用UIコンポーネントライブラリを管理しており,プライベートなnpmパケッジとして配布している.またherpismのStorybookは,社内のメンバのみがアクセス可能な環境にホスティングされている.

各フロントエンドのプロジェクトはherpismに依存している.

                               +---------------------+
                          +----|      Project A      |
                          |    +---------------------+
+-------------------+     |    +---------------------+
|      herpism      |<----+----|      Project B      |
+-------------------+     |    +---------------------+
                          |    +---------------------+
                          +----|      Project C      |
                               +---------------------+

またそれぞれのプロジェクトは,当該プロジェクト内でのみ用いられるUIコンポーネントを掲載したStorybookを持っている.

                               +---------------------+
                               | Project A Storybook |
                               +---------------------+
+-------------------+          +---------------------+
| herpism Storybook |          | Project B Storybook |
+-------------------+          +---------------------+
                               +---------------------+
                               | Project C Storybook |
                               +---------------------+

このような構成を取っているため,各プロジェクトのStorybookから,herpismに存在するUIコンポーネントを確認できると有用である.

                               +---------------------+
                          +----| Project A Storybook |
                          |    +---------------------+
+-------------------+     |    +---------------------+
| herpism Storybook |<----+----| Project B Storybook |
+-------------------+     |    +---------------------+
                          |    +---------------------+
                          +----| Project C Storybook |
                               +---------------------+

以下では,UIコンポーネントライブラリ(図中左側)を参照先,ライブラリを用いるプロジェクト(図中右側)を参照元と呼称する.また,参照元のStorybookはlocalhostで閲覧されることを前提している.

ドキュメント#

compositionについてのドキュメントとしては,以下の2ページが存在する.

前者は参照元の.storybook/main.jsに設定を書く手法であり,後者は参照先のパケッジのpackage.jsonに設定を書いておくことで,参照元のStorybookに自動的に読み込ませる手法である.個別のプロジェクトでの設定の手間を省くことができるため,今回は"package composition"と呼ばれている後者の手法を採用する.

package compositionを実現するためには,以下の手順を踏む必要がある.

  • package.jsonstorybookフィールドを追加する
  • stories.jsonを配置する
  • metadata.jsonを配置する(任意)
  • CORSに関する設定を行う

以下でhttps://storybook.example.comで参照先のStorybookがホスティングされていることを前提する.

package.jsonstorybookフィールドを追加する#

コンポーネントライブラリのpackage.jsonに,StorybookがホスティングされているURLを以下のように記載する.

このようなpackage.jsonを持つパケッジがnode_modules/配下に存在するプロジェクトでStorybookを立ち上げると,自動的にhttps://storybook.example.comでホストされているStorybookを埋め込もうとしてくれる.

stories.jsonを配置する#

Storybook内に存在するコンポーネントの情報を記載したstories.jsonというファイルが配信されている必要がある.@storybook/cliにはstories.jsonを生成するための$ sb extractというサブコマンドが用意されており,$ npx build-storybookで静的なStorybookをビルドした後,そのディレクトリを引数に与えて実行すると,ディレクトリ内にstories.jsonを生成してくれる.これはpuppeteerを通じてヘッドレスモードのChromiumが起動することで実現されている.

$ npx build-storybook -o ./dist
$ npx sb extract ./dist

うまくいけば以下のような内容をもつ./dist/stories.jsonが生成される.公式ドキュメントには以下のようなJSONが記載されている.

metadata.jsonを配置する(任意)#

公式ドキュメントには,compositionのためにはmetadata.jsonなるファイルが必要だと記載されているが,このファイルがどのようなものであるかについては言及されていない.加えて,このファイルの存在は必須ではなく任意である.GitHubのissue commentを参考にすると,以下のような内容であるべきだということがわかる.

versionsフィールドに複数のヴァージョンを記載し,異なるヴァージョンに対して異なるURLを割り当てておけば,参照元のStorybookからヴァージョンを切り替えることもできる.しかしHERPでの事例では,過去のヴァージョンのStorybookを半永久的にホスティングし続けるのが面倒なので,単一のヴァージョンのみを記載している.このようなJSONを生成する適当なスクリプトを用意しておき,ビルドプロセスに組み込んでおくのがいいだろう.

CORSに関する設定を行う#

Storybook compositionでは,参照元のStorybookを開いているブラウザからオリジン間リソース共有(CORS; Cross Origin Resource Sharing)を用いて外部のリソースを読み込むため,参照先のStorybookをホストしているサーヴァ側で,CORSを許可するよう設定を行っておく必要がある.

HERPではStorybookを配信するDocker imageを,Istioが入ったKubernetesクラスタにデプロイしているため,VirtualServiceに以下のようなCorsPolicyを追加することで対応した.また,oauth2-proxyを用いたアクセス制限を施しているため,allowCredentials: trueを指定している.

corsPolicy:
  # Access-Control-Allow-Credentialsヘッダに対応
  allowCredentials: true

  # Access-Control-Allow-Methodsヘッダに対応
  allowMethods:
    - GET

  # Access-Control-Allow-Originヘッダに対応
  allowOrigins:
    - regex: http://localhost:[0-9]+

  # Access-Control-Max-Ageヘッダに対応
  maxAge: 24h

前段に認証を挟む場合,Set-CookieヘッダにSameSite=Noneを指定することをお忘れなきよう.

設定がうまくいっていれば,参照元のStorybookを立ち上げた際に,サイドバーの下部から参照先のStorybookを参照できるようになっているはずである.

Referenced Storybook