電動キックボードが気になってはや1年、こうなるともう一生乗ることはない気がしている今日この頃ですが、皆さんはいかがお過ごしでしょうか。
どうも、技術本部Strategic Products Engineering Unit Contract One Devグループの原です。
Contract Oneに入ってからだいたい2年になりますが、今はテクニカルリードとして技術的負債の解消や生産性の向上についての活動をしながら、時たま大きめの機能開発のプロジェクトリーダーなんかもやったりしています。
今回はそんな僕の活動の中から、生産性の向上に関する取り組みを1つご紹介します。フロントエンド側の開発に焦点を当てて、StorybookとChromaticをうまいこと活用して、デザインの品質向上と実装時の開発効率向上を目指した一石二鳥の施策に取り組んでみた話です。
なお、本記事は【Strategic Products Engineering Unitブログリレー】という連載記事のひとつです。 buildersbox.corp-sansan.com
フロントエンド開発時の課題
デザインの品質担保
継続的にContract Oneを開発していると、時には全ページに影響がある修正も発生しますし、それに伴うリスクもあります。
例えば使っているライブラリのバージョンアップなんかは、新しい機能や直されたバグなどを取り込むために適度なタイミングでやっていきたいところですが、バージョンアップによる画面の表示崩れなどのリスクがあります。
また、多くのページで使用する共通のコンポーネントを修正するときも同様のリスクがあります。
Contract One Devグループではこういった全ページに影響がありそうな修正を行ったときは手動でテストを行っていて、毎回各チームで分担してやっていました。ただ、そうすると作業コストは大きいですし、作業を調整するためのコミュニケーションコストもかかります。
テストのためのコストが大きいので、共通のコンポーネントを修正するのが億劫になりがちです。そのため、他のページに影響が出ないように該当コンポーネントをコピペしてから修正することで、同様のコンポーネントが複数できてしまうこともありました。イメージでいうと以下のような感じです。
src/contract/search/SmallButton.tsx src/organization/user/list/SmallButton.tsx src/setting/extension-fields/SubmitButton.tsx
これでは、本来1つの共通コンポーネントで管理したいUI要素を多頭飼いすることになってしまい、UI要素のデザイン変更や、内部で使っているUIライブラリの移行時などに不便です。
今後もライブラリのバージョンアップや内部のリファクタリングはやっていきますし、ページの数も増えていきます。こういった手動テストは自動化して、表示崩れを検知するテストを効率的に行う必要がありました。
開発効率
APIと通信して機能を提供するフロントエンド側を実装する場合、面倒なことが1つあります。それは、作りたい機能のAPIがまだ実装されていない時にフロントエンドを実装すると、動作確認が難しいという点です。
Contract Oneでは、ローカルPCにて複数のバックエンド側アプリとPostgreSQLを立ち上げ、フロントエンド側アプリを立ち上げれば一通りの動作確認はできます。ただ、APIがまだ実装されていなければ当然フロントエンド側も単純には動作確認できません。そんな時、エンジニアがよくやる行動パターンとしては以下のようなものがあるかなと思います。
- APIが実装されるまで待ってから実装する
- API通信に関係しない、機能を構成する小さいコンポーネントを先に作っておく
- APIから取得するデータはとりあえず固定値でハードコーディングして進める
1は、解決方法としては単純ですが作業を直列に進めることしかできなくなるので、並列作業で開発を早く終わらせるといった手段が取りづらくなります。1スプリント内で大きめな機能を作り切らなければいけない時は特に不便かなと思います。
2は、後述するStorybookを使って先に小さいコンポーネントから作っておき、後で結合して1つの機能にするというやり方です。これだとある程度並行して作業ができます。ただ、最終的に1つのページに結合していく過程で「デザインに問題がないか、動きが正しいか」を確認したい時にAPIは必要になってきます。
3は、APIからデータを取得する部分でとりあえず固定値を返しておくので、並列して作業ができます。ただ、これだとハードコーディング部分の管理が面倒です。
プルリクを出すときにハードコーディング部分を消すとレビュワーが動作確認に苦労するし、ハードコーディング部分をそのままにしてレビュワーに理解してもらうのもちょっと面倒です。
また、APIが既にあったとしても、画面表示の確認のためにバックエンド側のデータをローカルPCで何パターンか下準備しなければならないので、実装者やレビュワーにとってはそれも面倒ではあります。
その場で工夫をすれば効率的に開発を進められるとは思いますが、いかんせんフロントエンド側の実装プロセスをバックエンド側から分離させたほうが効率は良さそうです。
今回使うStorybookとChromaticについて
上記に挙げたような課題をStorybookとChromaticを組み合わせて解決していくのですが、その前にStorybookとChromaticについて軽く説明します。
Storybook
Storybookは、フロントエンド開発においてコンポーネントの開発、テスト、ドキュメント作成を効率化するためのオープンソースツールです。コンポーネントを「ストーリー」という形式でカタログ化したり、アプリケーションとは独立した環境でコンポーネントを動作させて開発、テストができます。コンポーネント単体をストーリーにして動作させることもできますし、フロントエンドのアプリケーションを丸ごとストーリー化させることでページ単位での動作確認も可能です。 storybook.js.org
Chromatic
ChromaticはStorybookの機能を拡張するSaaSで、VRT(Visual Regression Testing)や、UIレビュー、Storybookのホスティング、CI/CDとの統合が可能になります。Storybookにはクリックや入力操作をシミュレートした機能面のテスト(Interaction tests)を行える機能がありますが、それをChromatic上で実行させることによりCIテストもできます。 www.chromatic.com
改善策
開発者体験を考慮して改善策を考える
実は今回の件に関しては、当初は表示崩れ検知のためのVRTの取り組みを推進するためにStorybookとChromaticを活用しようと考えていました。これを実現するに当たってすることといえば、
- APIのモックを用意する
- Storybookを使って対象ページ*1の初期表示のストーリーを作成する(このときに作成したAPIモックを使う)
- 該当ページの他の表示パターンのストーリーも作成していく
くらいかと思います。
VRTをしていくのであれば上記のことをやり続ければよいのですが、Contract Oneの機能開発が忙しいときは、こういった施策は忘れ去られてメンテナンスされなくなるリスクがあります。なので、今後も忘れられずに継続してメンテナンスがされるように、「やることに対するインセンティブ」をもう少し盛り込んだ仕組み作りをしたほうが良いと考えました。
当時はちょうどフロントエンドの開発効率にも課題感があったので、この改善も盛り込んで、開発者にとって扱いやすくインセンティブもある形にするため、以下のような観点を大事にしました。
- ページのストーリーは簡単に作れるようにする
- まずはストーリー作成の敷居を下げる
- VRT用に作ったページのストーリーはローカル環境での開発にも活用できる
- バックエンド側に依存せずフロントエンド開発ができるメリットにより、ストーリー作成のモチベーションを上げる
- 作成したストーリーは自動でChromaticにホスティングされ、実際の画面を簡単にデザイナーに見せられる
- デザインレビューの体験を向上させてChromatic運用のモチベーションを上げる
簡単にVRT用ページストーリーが作れるようにする
VRTのために使用するページのストーリーをVRT用ページストーリー
と呼んでいます。
最終的には以下のようなコードを書くだけでVRT用ページストーリーが作れるようになりました。
export default { title: "vrt-pages/contracts/list", ...appMeta({ vrtEnabled: true }), } as Meta<typeof App> export const Default: AppStory = { name: "初期画面", parameters: { router: { initialEntries: ["/contracts/list"], // 表示したいページのURLを指定する }, msw: { handlers: { ...postMockSearchContracts(), ...getMockMyGroup(), // 以下、APIモックのメソッドを必要なだけ差し込んでいく... }, }, }, }
これには、各機能のページコンポーネント*2をストーリーにするのではなく、フロントエンドアプリケーションを丸ごとストーリーに載せて、URLによって表示するページを指定することで実現しています。*3
また、appMeta
やAppStory
はページのストーリーを作るときのお決まりの記述を書かなくてもいいようにするラッパーとなっています。毎回書くのは面倒ですからね。
// 引数の vrtEnabled によってVRT対象のストーリーにするかを選べる export const appMeta = ({ vrtEnabled = false, chromaticViewport }: Args): Meta<typeof App> => ({ component: App, // これが大本のフロントエンドアプリケーション本体 parameters: { layout: "fullscreen", controls: { disable: true, }, actions: { disable: true, }, chromatic: { disableSnapshot: !vrtEnabled, modes: { desktop: { viewport: chromaticViewport ?? 1920, }, }, }, }, }) export type AppStory = StoryObj<typeof App>
APIモック
APIモックはMSWを使って以下のような感じで作っています。
引数を与えることで任意のダミーデータを返せるようになるし、指定がなければとりあえず適当なデータを返すようになっています。このおかげで画面表示パターンを変えたいときも引数に渡すダミーデータを変えるだけでいいので楽になります。
type Group = { id: string name: string } const dummyGroup: Group = { id: "123", name: "ダミーグループ", } export const getMockMyGroup = (args?: Partial<Group>) => ({ "GET /api/group": rest.get("/api/group", (_, res, ctx) => res( ctx.json<Group>( args ? { ...dummyGroup, ...args } : dummyGroup ), ), ), })
このように簡単なコードでページのストーリーが書けることで、毎回ストーリーの作り方に悩まなくて済みますし、どのページのストーリーも書き方が統一されて見やすくなりました。ほぼコピペで作れるので早く作れて楽です。
ローカル環境での開発にもストーリーを活用する
開発効率の課題のところでも述べたように、フロントエンド側の実装プロセスはバックエンド側から分離させたほうが効率は良さそうです。今回作成したページのストーリーを使えばAPIがモックされたフロントエンドアプリケーションが手に入るので、これを使えばバックエンド側のアプリケーションに依存しない形で開発できます。
また、VRT用ページストーリーと混ざってしまうと面倒なので、ローカル環境での開発に使うストーリー(以下、動作確認用ページストーリー
と呼ぶ)は格納ディレクトリを別に設けます。
src/_storybook ├── mock // APIモックの置き場所 ├── vrt-pages // VRT用ページストーリーを置く └── preview-pages // 動作確認用ページストーリーを置く
元となるストーリーはVRT用ページストーリーを流用すればいいだけですので簡単です*4。実際に開発するときの流れは以下の通りになります。
- VRT用ページストーリーを
preview-pages
ディレクトリに複製する - 複製して作った動作確認用ページストーリーを使って実装作業を行う(動作確認に利用する)
- プルリクのレビュワーやデザイナーに見てほしいデザインが複数ある場合は別途ストーリーを作ってもOK
- 動作確認用ページストーリーも含めてプルリクを出す(CI経由でChromaticによってStorybookのホスティングも行われる)
- レビュワーやデザイナーが動作確認用ページストーリーを使いながらレビューをする
プルリクのレビュワーが動作確認をするには、前まではレビュワーのローカル環境にてバックエンド側に確認用データをいろいろと用意する必要がありました。今回の方法では実装者が用意してくれた動作確認用ページストーリーがあるのでその手間が省けます。
デザイナーとのデザインチェックも今まではスクショや動画を撮ってSlackで送るかステージング環境でデプロイしてから見てもらっていました。今回の方法によって、ChromaticにホスティングされたStorybookのおかげで動作確認用ページストーリーをいつでも見られるので、実際に動かしながらチェックができます。また、ステージング環境の利用スケジュールを気にする必要もありません。
プルリクを出した時にChromaticにホスティングされたStorybookのURLを探すのが面倒だったので、Github Actionsを使ってURLをコメントに投稿してくれるような仕組みも作っています。
preview-pages
に置いた動作確認用ページストーリーは開発が終わった後は不要になるため、一時的なストーリーという位置付けにして不要になったら適当なタイミングで削除するルールにしました。
普及活動
仕組みを作っただけだと開発メンバーは誰も使ってはくれません。開発環境の改善の仕組みを作るなら、それを根付かせるための普及活動がとても大事です。
今回のStorybookとChromaticの導入でもいろいろと普及活動を行いました。
利用ガイドラインの整備
ストーリーの作り方はサンプルコード付きで解説する感じの説明を書きました。また、説明するときに用語が統一されていないとややこしくなりそうなので統一しています。例えば、VRTのために使うページのストーリーはVRT用ページストーリー
、ローカル開発時に使う動作確認用途のページのストーリーは動作確認用ページストーリー
と名付けています。
それと、ストーリーを作るときのディレクトリ階層やファイル名、ストーリータイトルなんかは全員で共通したルールを持っていないとストーリーの管理ができなくなるため、ここはしっかりとルールを策定しました。
事前のストーリー作成
本当はすべてのページに対してVRT用ページストーリーを事前に作ってから今回の施策を開始したかったのですが、すべてのページを相手にすると結構な数になるため工数の関係上できませんでした。
ただ、施策の開始時にVRT用ページストーリーが1つもない状態だと、いざストーリーを作るときの敷居が高くなってしまいます。あらかじめVRT用ページストーリーをいくつか用意しておけばどんな感じで作ればいいか雰囲気がわかりますし、真似して作るための材料にもなるので、可能な限り事前にいくつか用意することにしました。
事前にVRT用ページストーリーを用意する対象のページに関しては、直近で開発プロジェクトが発生しそうなページを調査したり、各チームに対して直近どのページを弄りそうなのかをヒアリングして絞り込みました。直近で触りそうなページに対して作っておけば、動作確認用ページストーリーとしてすぐに流用できますからね。
説明会やハンズオン会
ガイドラインや事前のストーリー作成をしても、実際に各チームに展開してみたら不明点や不足しているところも出てくると思います。そのため、今回の施策の意図や目的、実際の使い方など、みんなの理解を深めたりフィードバックを受け取るために説明会やハンズオン会も行いました。
また、今回の施策に詳しいエンジニアを各チームに1人置き、実際の開発作業中にサポートもしています。
現在の活用状況は?
VRT
まだ施策を始めたばかりなので、VRT用ページストーリーはまだすべてのページに対して用意できておらず、本格的なVRTはもうちょっと先になりそうです。今は全ページのうち50%ほどがVRTが実施可能になっており、引き続き各チームで分担して作成している最中ですが、今年の秋くらいにはすべて揃えたいなぁ、という気持ちです。
ただ、ストーリーの作成は各チーム慣れてきた感じで、今のところ作成中の大きな問題は発生していません。もし作成方法が複雑だと説明会の実施やサポート体制のコストが結構高くなってしまっていたと思うので、簡単にストーリーが作れるように工夫したのは功を奏したと思います。
ローカル環境での開発効率
動作確認用ページストーリーを使ったフロントエンド開発の効率化は一定の効果が出てきているようで、Contract Oneのメンバーからはポジティブな声をもらっています。
今まではバックエンド側アプリを複数立ち上げたりPostgreSQLに動作確認用のデータをいろいろ仕込んだりと、画面の動き1つ確認するにも面倒でしたが、APIをモックすることでバックエンド側の準備の煩わしさからは解放されました。引き続きこれを活用してフロントエンド開発のリードタイムを縮めていきたいと思います。
まとめ
今回は、StorybookとChromaticを活用することでフロントエンド開発の品質と開発効率を向上させる施策をご紹介しました。
StorybookとChromaticを活用することでフロントエンド開発の機動力は結構上がったと思います。近々VRTも本格的にできると思うので、これを機にして今後は積極的にライブラリのアップデートやリファクタリングを推進していき、品質と開発スピード、そして開発者体験を向上させていきたいです。
今回の記事が、StorybookとChromaticを導入する際の参考になれば幸いです。
それと、Contract One では一緒に働く仲間を募集しています!
詳しくはこちらのリンクから採用情報をご確認ください。
*1:ちなみに小さいコンポーネント単位でVRTをすると、それらを結合したときの表示崩れが検知できないしChromaticの料金もかかるので、ページ単位でストーリーを作成します。
*2:ページを構成するUIコンポーネントを組み合わせて1つのページを作り上げるコンポーネントのこと。
*3:渡されたURLの情報はStorybookのDecorator内にあるMemoryRouterに流しています。
*4:流用するときにVRTの対象にはしたくないので、ストーリーのVRT除外設定を使用してVRT対象外にしています。