🎨

社内UIライブラリを作る

2023/04/29に公開

こんにちは、monica です。
今回は弊社で開発している社内 UI ライブラリについて紹介します。

また、このプロジェクトは商用利用不可ですがオープンソースで公開していますので、ぜひご覧ください。

https://github.com/wizleap-inc/wiz-ui

経緯

弊社ではこれまでフロントエンドを Vue2+ElementUI で開発してきました。
しかし Vue3 がリリースされ Vue2 のサポートが 2023 年末に終了することが決まったので、マイグレーションを行う必要がありました。

普通に Element UI を使っていれば、公式が提供している Migrator を使ってそこまで不便なくマイグレーションできます。
ですが勝手な外部からのスタイリングが当たっていたり、依存関係をどんどん増やしてしまった結果、Migrator を使うとアプリ全体が壊れるという状況になっていました。

ただたまに Element UI が直接スタイルを当てるように設計している場合もあり、単に自分たちのコードが悪かったわけではなかった部分もあります。

コンセプト

  • Vue や React、もしくは今後新たなフレームワークが出てきたときにも対応できるようにする。
  • UI 開発に必要な知識(HTML/CSS)がないメンバーでもフロントエンドの開発へ参加できるようにする。
  • Vue2 -> Vue3 のマイグレーションを限りなく 0 コストにする。
  • 複数プロジェクト間でのコピペをなくす。

を元にアプリケーションから UI のロジックを切り離し、社内製品 UI の一元化を目指しました。

開発環境

Component Catalog

Storybook では 1 つのファイルに複数の Story コンポーネントを書くのが一般的です。
しかし、Vue はデフォルトで SFC(Single File Component)という 1 つのファイルに 1 つのコンポーネントを書く形式を採用しています。
そのため、Storybook で Vue を使う場合は SFC ではなく new Vue() を使って JS のみで書く必要があります。

そこで私が Histoire という最近出た Vite 製で Vue 向けの Story があることを知っていたので、それを使おうという話が出ました。
当時開発されたてだったこともあり周辺プラグインでバグが多く私も Visual Regression Test 用の Capture Plugin にコミットしたりしました。
しかし結局 React 化対応の話が出た時に対応できないことがわかり、Storybook を選びました。

https://github.com/histoire-dev/histoire/pull/299

つい最近 Storybook V7 がリリースされましたが、開発初めの段階では V7 は alpha 版でした。
V7 では vite-builder が公式から提供されたり、Storybook が複数のフレームワークを持つ monorepo で管理しやすいようになったりと、開発体験が大幅に向上したそうです。

詳細はこちらの記事を参照して下さい。
https://zenn.dev/sterashima78/articles/3ae177ebc06bc5

この恩恵にあやかって、我々は alpha 版の頃から Storybook V7 での開発を進めてきました。

Monorepo

Monorepo で管理することで、共通のコードを private な package として共有できるようになります。
今ある package は以下の通りです。

  • packages/
    • wiz-ui-styles (Vanilla Extract)
    • wiz-ui-icons (SVG + generator)
    • wiz-ui-utils (Functions)
    • wiz-ui-constants (Color Tokens, Size Tokens, etc.)
    • vuepress-plugin-wiz-ui Vue2
    • wiz-ui (Vue2)
    • wiz-ui-next (Vue3)
    • wiz-ui-react (React)
  • examples/
    • vue2 (Vue2)
    • vue3 (Vue3)
  • docs

UI フレームワークである wiz-uiwiz-ui-nextwiz-ui-react を公開。
さらにそれぞれの内部で wiz-ui-styleswiz-ui-icons を呼び出しています。

また、管理にはこれらのツールを使っています。

  • pnpm (パッケージマネージャー)
  • turborepo(scripts 管理、ビルド最適化)
  • changeset(セマンティックバージョニングと Changelog 生成、破壊的変更の管理)

以前 yarn を使っていたのですが、pnpm に移行することでこれらの恩恵を受けられました。

  • ノンブロッキングなインストール方法による高速化
  • ローカルキャッシュによる高速化
  • 厳格な node_modules 配下のモジュール管理(シンボリックリンク)

Changeset についてもなかなか苦戦したのでそのうち記事にします。

Styling

CSS をそのまま書くと型がなく、開発体験が悪いのでどうにかして @types/css の恩恵を受けたいと思い今回はVanilla Extractを使いました。

「いやスタイリングでライブラリ使うんかい」と思われるかもしれませんが、Vanilla Extract 自体はビルド時に CSS へと変換されるので実行時には CSS を読み込むだけで済みます。
(Vanilla Extract はこれを Zero-Runtime CSS-in-JS ライブラリと謳っています)

したがってパフォーマンスで心配する必要はありません。
ただ保守性を高めるように拡張された型付きの CSS のイメージです。
構文サポート+プラグインで提供されているので、プラグイン次第でどのフレームワークでも使えます。

CI

CI を強固にして、ライブラリの品質を担保します。
導入したものは以下の通りです。

  • Lint (ESLint)
  • Test
    • Unit Test (Vitest)
    • Visual Regression Test (Storybook, Reg-Suit)
  • Build (Bundle Analyzer)
  • Publish (Changeset)
  • Storybook Deployer (AWS)
  • Coverage Deployer AWS

特に Visual Regression Test は、Storybook で作成したコンポーネントのスナップショットを取り、変更があった場合には PR 上で確認できるようにしています。
これによって意図しないデザイン崩れを防ぎながら開発できます。

社内インフラが AWS で統一されてますので、自動デプロイシステムは Cloudfront + WAF + S3 で自作しました。
Vercel などの CI 連携サービスを使えば PR 単位の Storybook デプロイがとても楽にできるので、今後そちらに移行しようという話も出ています。

Icons

Icon は google の material design icons から持ってきています。
これらの Icon は Apache License Version 2.0 で公開されているので、この SVG を改変して使っています。
具体的には private package の icon-generator でそれぞれのフレームワークのフォーマットに変換して出力しています。
基本は SVG をそのまま HTML に埋め込む形ですが、外部からサイズ等を変更できるように拡張しています。

開発体制

チーム構成

  • 社員 1 名
  • インターン 1 名(私) + 途中参加の方 1 名
  • 業務委託の方 2 名

進捗管理

1 週間単位でのスプリントを設け、スプリント末ごとにデザイナーとエンジニアによる MTG をしています。

  • 本番環境で出た不具合や、デザインの修正などを Issue にして Feedback してもらう
  • Issue を Github Project の Kanban に追加
  • 1 週間ごとに issue のラベリングをしてリソースの割り当てをする

という流れで進めています。

Github Project

まとめ・感想

改善できたこと

製品のフロント開発

製品の方のフロントエンドを開発していただいているエンジニアを対象に、Wiz UI の開発体験についてアンケートを取りました。
以下がいただいた Feedback の抜粋になります。

feedback

生の CSS や UI ロジックを意識せずにフロント開発ができるようになったという意見が 6 割以上を占めていました。
これは UI やスタイリング、デザインロジックのカプセル化によって、デザインが抽象化された恩恵だと考えています。

また、開発速度が向上したという意見や、UI が分離されたことでフロントのアーキテクチャがより簡潔になったという意見もいただきました。

ライブラリの管理

このプロジェクトが始動した 1 つの要因として、先ほども述べたような依存関係の多さからくるマイグレーションの煩雑さがありました。
そのため私が Join して以降、依存関係を増やすハードルや管理の大切さについて頒布するよう心がけています。

  • ライブラリを増やす場合は必ず複数人で議論する
  • 独自実装で補えないか検討する
  • メンテナンス頻度やサポートについて調査する

などを考慮するようになりました。

今後の課題

A11yの最適化

本来Radioで実装されるべきTabがdiv + onClickで実装されているなど、まだまだ改善の余地があります。
色々な環境下のユーザーに対して、より使いやすい UI を提供できるようにしていきたいです。

テストの充実

現状 Visual Regression Test のみ稼働しています。
意図しないようなデザイン崩れは抑制可能ですが、コンポーネントの挙動についてはテストが不十分です。
現状 Storybook の Play フィールドに test を書いているのですが、SB に依存する形となっています。
今後は test を外部(Vitest)に切り離し、ユーザーの操作に対してInteraction Testを実装していきたいです。
Popup など複雑な Window 依存のコンポーネントでは特に重要になってくるでしょう。

開発体制

エンジニアリソースが足りず今までレビューや開発をほとんど一人で担当することが多くいわゆるワンオペ状態になってしまっていました。
(現在私は主力のコミットから離れ、開発のサポートやレビューを中心に行っています。)
今後はより多くの人に開発を割り振ったり、レビューワーをローテーションさせたりしていきたいです。
また、インフラや環境の知識は完全に自分しかない状況なので、これもドキュメントや導入理由などをドキュメントに残していきたいです。

ワンオペ

GitHubで編集を提案
Wizleapテックブログ

Discussion