Testlifyは4つの独立したWebアプリケーションからなるスイートとして運用されています。以前は各プロダクトチームが共通のUIコンポーネントをそれぞれ独自に開発・スタイリングしており、エンジニアリング作業の重複と、プロダクト間での見た目の不一致を招いていました。私はこのアイデアを提案し、UI開発を標準化するための共有Vue 3コンポーネントライブラリを構築するチームを率いました。
作ったもの
- アーキテクチャの統合: 共通のUIプリミティブを中央集権的なライブラリに統合する取り組みを主導し、チーム横断で一貫したデザイントークンとコンポーネントの契約を確立しました。
- 状態を持たないコンポーネント設計: ライブラリのコンポーネントを厳密に状態を持たない、見た目だけを担うものにし、ビジネスロジックを切り離したまま、4つのアプリケーションのどれにもきれいに組み込めるようにしました。
- プライベートパッケージとしての配布: ライブラリをバージョン管理されたパッケージとしてGitHub Packages上で公開・保守し、各チームがコードの重複なしに、通常の依存関係の更新を通じてバグ修正や新しいコンポーネントを取り込めるようにしました。
なぜ意味があったか
UI開発を共有パッケージに集約したことで、4つのアプリケーション全体で重複したコードがなくなり、機能提供のサイクルが速まり、Testlifyのプロダクト群全体で一貫したデザインシステムが保たれるようになりました。
主導したが、一人ではない
私はこのライブラリを提案しアーキテクチャを主導しましたが、これは一人で作ったものではありません。2026年5月以降、7人のコントリビューターによる86コミットがあり、私自身のコミットが全体のおよそ3分の2を占めています。共有デザインシステムを4つのプロダクトチームに導入してもらうには、コンポーネントを書くだけでは足りません。それらの採用をリスクの低いものにするリリースプロセスが必要で、それこそが実際に難しい問題でした。
すべてのPRで公開せずに未リリースのコンポーネント変更をテストする
共有ライブラリの採用における最大の障壁は、コンポーネントを書くことではなく、「何かを変更した」ことと「利用側のチームが実際に試せる」ことの間のギャップです。すべてのPRで一時的なバージョンを公開するのは、ほとんどのPRがどこにもインストールされることのないパッケージに対して、実際にかかり続けるCIコストになります。そこでこれはオプトインになっています。利用側が実際にテストを必要とするときだけPRにneeds-snapshotラベルが付き、それがsnapshot.ymlというGitHub Actionを起動して、一時的な@pr<番号>タグ付きパッケージを公開します。対になるcleanup-snapshot.ymlが、PRがクローズされたらそのタグを削除します。通常のリリースはChangesetsを通じて2つのチャネルで行われます。mainからの@latestとステージングブランチからの@betaです。リリースワークフローは、CIが通ると自動生成された「Version Packages」PRを自ら自動マージします。
自身の依存関係の方向を強制するモノレポ
このライブラリは1つのフラットなパッケージではなく、層に分かれています。tokens、icons、stylesが内部依存を持たない最下層にあり、coreがその上に構築され、dsがcoreの上に、ds-patternsがds、core、stylesの上に構築されます。各層は下方向にしか依存できず、上方向には依存できません。そのため低レベルのプリミティブが、気づかぬうちに上位のパターンコンポーネントへの依存を持ってしまうことはありません。ds-patternsは汎用的なプリミティブだけでもなく、実際のTestlifyドメインのウィジェット(例えばCandidateCard)も含んでおり、これも他のすべてが使うのと同じプリミティブ層の上に構築されています。アクセシビリティアドオンを有効にしたStorybookがすべてのコンポーネントを文書化しており、公開デプロイされているので、利用側チーム(あるいは社外のレビュアー)はリポジトリを取得しなくてもコンポーネントの状態とa11yの結果を確認できます。すべてのコンポーネントには、手動確認のためのStorybookアドオンだけでなく、CI上で実際のaxe-coreチェックを実行する専用の.a11y.spec.tsファイルも備わっています。
データモデルと規模
- 6つの公開パッケージ(
ds、-core、-icons、-patterns、-styles、-tokens)があり、それぞれ独立してバージョン管理されています。 - 利用側のアプリだけでなく、ライブラリ自体を直接検証する243個のspecファイル(Vitest + Vue Test Utils)があります。
- CI、プレビューと本番のデプロイ、Storybookのデプロイ、ステージングと本番のリリース、PRスナップショット、スナップショットのクリーンアップをカバーする9つのGitHub Actionsワークフローがあります。
- pnpmワークスペースとTurborepoのキャッシュにより、ビルドタスクは実際に変更の影響を受けるパッケージだけに絞り込まれます。テストはTurboのタスクグラフを介さず、ワークスペース全体に対してVitestで直接実行されます。
導入は強制的な一斉切り替え日ではなく、チームごとに展開されました。リクルーター/管理者アプリは現在6つの公開パッケージすべての上で動作していますが、他のプロダクトチームは4つのアプリケーション全体を一度に協調して切り替えるのではなく、それぞれ自分たちのリリーススケジュールに沿って個々のコンポーネントを取り込んでいます。
自作か既製品か
手作りのバージョン更新スクリプトはありません。Changesetsが、PRごとの1つのchangesetファイルから、6つのパッケージすべてにわたるバージョニングとチェンジログを管理します。自前のプライベートレジストリもありません。GitHub Packagesは、Verdaccioのような別のレジストリを運用・保護する代わりに、すでにリポジトリへのアクセスを制御している同じGitHub認証を再利用しています。
Stack
Vue 3、TypeScript、Vite、Turborepoを伴うpnpmワークスペース、アクセシビリティアドオンを伴うStorybook、バージョニングにChangesets、VitestとVue Test Utils、GitHub Packages経由で公開。