Storybookをデプロイした話

そもそもなんでStorybook入れたのか?? Storybookの目的とは

  • 社内でのUI仕様の確認
    • 色んな人が、UI仕様を簡単に確認できる環境があったら便利。
    • 例えば、ユーザがこのステータスのとき、配送日変更できるのかとか、すぐ知りたいときに、ユーザの状態を再現しないと確認できないのは避けたい
  • 開発効率改善
    • UIの再現が楽なので、UIの開発が効率が上がる。具体的には、修正とレビューとテストが楽になる。
    • デプロイに関係するところで言うと、PRを楽にしたかった。
      • PR作成時に、完成形のスクリーンショット貼るのがしんどい。
      • PRレビュー時に、ローカルにクローンして、レビュイーと同じUI環境を再現するのがしんどい。

デプロイ環境の選定

要件を満たすためしたいデプロイ環境は2つ

  • 色んな人が見れる、mainブランチのStorybookのデプロイ
    • 社外の人から見れない
  • PRごとに作成される一時的なStorybookのデプロイ
    • 複数のPRがあっても、かぶらないこと

Storybook デプロイ環境の選定をしていく

ちなみに、Storybookは、サーバサイドを伴わない、ただのReact(html + js + css + assets)

先に結論! Storybookのデプロイ環境は、chromaticと、Github Pagesと、ホスティングサービス(AWS S3, Firebase Hosting )などが考えられ、今回はFirebase Hostingを選定した

  • chromatic https://www.chromatic.com/
    • 特徴
      • 公式で案内しているStorybookデプロイ環境
      • チームのみがアクセスできるStorybook環境
      • 簡単デプロイ & 簡単テスト: デプロイ時、毎回スナップショットをとって、自動差分テストしてくれる (スナップショット月5000枚まで無料)
      • デザイナーさんとコミュニケーション取りやすい。 デザイナーさんとコミュニケーション取るため、UIにポストイットようなコメントを貼れる
    • 選定結果→不採用
      • 個人的に好みではないコンポーネントの選択のUI。Storybookのサイドバーのアコーディオン形式のディレクトリ表示が好きだったのに、ディレクトリごとにページが有るのは触りづらい
      • chromaticは、リッチすぎる。デザイナーさんとのコミュニケーションは課題ではない
      • フリープランは容易に使い切って課金になりそう。
      • → リッチすぎるので、使わない
  • Github Pages
    • 特徴
      • Githubのサブな機能で、ホスティングできる
      • リポジトリとまとめて管理できるので、リソースが散らばらないのはかなり良い
      • ただ、現状のプランだと社外から丸見え。見えなくするには、エンタープライズのプラン変更でお金かかる
    • 選定結果 →不採用
      • Storybookのためにエンタープライズのプラン変更するのはきつい
  • その他ホスティングサービス (AWS, Firebase)
    • AWS
    • Firebase
      • Firebase Hostingとか
      • awsデプロイ記事よりは記事が多くないが、問題なくできそう
      • Github ActionでPRごとにhostingする機能もある&
    • 選定結果 → Firebase Hostingを採用
      • 使い慣れているので、Firebase Hostingを採用する。
      • ホスティングサービスはどこも正直似たりよったりだと思っているので、慣れているのを使うのが一番良い

mainブランチのデプロイ

構成

社内の色んな人が見れて、社外の人から見れないを実現するためには、認証機能が必要 Basic認証に成功したら、storybookを返す関数をfirebase functionに実装した

flowchart LR

subgraph GST[Firebase]
  FCF[Firebase Cloud Functions]
	Auth["認証(express)"]
  SB[Story Book]
end

User --> FCF
FCF --> Auth
Auth --> SB

やり方

実装

以下のライブラリを使って、ベーシック認証と、静的ファイルの配信を行っている

express

basic-auth-connect

expressなくてもベーシック認証ができるが、静的ファイルの配信をするのが簡単にできるので採用している

また、Cloud Functionsでは、expressをそのまま返すことができるので、簡単に使えて便利

import * as functions from 'firebase-functions'
import * as express from 'express'
import * as basicAuth from 'basic-auth-connect'

// 認証情報
const userName = '****'
const password = '****'

const app = express()
// 認証を付ける
app.all(
  '/*',
  basicAuth(function (user: string, pass: string) {
    return user === userName && pass === password
  }),
)

// storybookを、静的ファイルとして返す
app.use(express.static(__dirname))
// 関数を公開する
export const storybookWithAuth = functions.https.onRequest(app)

参考:

ビルド

  1. storybookを、functions/libにビルド結果を出力(ここがポイント。)
  2. cloud functionsをビルド
  3. functions/libをデプロイ

↑をGithub Actionでmainにマージタイミングで走るようにする

詰まった箇所

storybookのビルド出力先が味噌。

  • 最初は、functions/lib/publicやfunctions/lib/staticにビルド結果を出力してた。Storybookのアセットのパスが解決できず no imageになっていた。
  • storybook内で使っている画像などアセットパスが、expressの静的ファイル配信場所に合わせて変更できない
  • 以下の方法でかいけつ
    • ファイル直下を配信する app.use(express.static(__dirname))
    • storybookのビルドの出力先を、functions/lib にする

Firebaseの設定ファイル書き方に気をつける

Firebaseの設定ファイルの書き方が悪くて、Github Actionで、デプロイされないことがあった。何度書いてもミスする不思議。

2 PRごとのデプロイ

公式Firebase Hostingで紹介されているGithubアクションをそのまんま利用

構成

flowchart LR

subgraph GST[Firebase]
  FH[Firebase Hosting]
  SB[Story Book]
end

User --> FH
FH --> SB

まとめ&今後

  • Firebaseの機能を使うことで、高いお金を払うこともなく&セキュリティを損なうこともなく、Storybookでデプロイしたい要件を満たすことができた。
  • Firebase Cloud Functionsはやっぱり便利。Firebase Cloud Functionsとexpressの相性がいいのを知らなかったので、知れてよかった
  • 現状、Storybookを使い始めたばかり&エンジニア内でしか使っていない。Storybookがもっと全社的に使われるorデザイナーさんとコミュニケーションとして使うなど、文脈が変わってくれば、もしかしたらデプロイ環境を考え直す必要があるかもしれない