🤝

共通UIコンポーネントを導入してみた

2022/11/29に公開

はじめに

複数画面が存在するSPAの構成でWebアプリを作成するときいつも重複コンポーネントについて議論しがち。。。そしてとりあえず別々で各アプリに作成し、なんとなくメンテしていく流ればかりをとっていました。しかし、実装が進めば進むほどメンテコストが増えるばかり。そんなときに意を決して共通コンポーネントを作成してみたところ思いのほかかなり良かったので残しておこうと思いました。同じような境遇の方の参考になれば嬉しいです。

経緯

Next.js のSPA構成で ChakraUI を用いて3画面分のアプリを立ち上げるプロジェクトでした。構成を考える上でいつものごとく重複コンポーネントをどうするかの議論を交わしていたのですが、何かとマンパワーになりがちなので試しではありましたが今回は思い切って共通化を選択することとなりました。

今までやりがちだった構成

  • 各画面で共通パーツがあってもコピペして各リポジトリに増産
  • 立ち上げの時にはコピペで終わるので意外と楽
  • 時間が経って共通パーツに手を加える場合にメンテコスト3倍

今回試みた構成

  • 各画面の共通パーツを集めた共通コンポーネントを作成
  • リポジトリが1つ増えるのでまぁまぁ手間?
  • 時間が経っても手を加える場所が1つなのでとっても効率良いかも?

導入から運用までの流れ

模索しながらだったため手順として書くのは少々おこがましい気がしたので事実として書き連ねていきたいとおもいます。

デザイナーさんとお話しする

3画面分を実装する上ですべての画面が異なるデザインの場合、共通化は不可能です。そのため、方針を揃えておく必要がありました。難なく共通化のOKをもらい、デザインシステムの隅々まで作っていただけたのでとてもスムーズでした。

ディレクトリ構成を決める

共通化といえども3画面分のコードを書かなくてはいけないため主にコンポーネントの構成をしっかり決めておく必要がありました。アプリ側のコンポーネントの構成をよしこさんの記事を参考に決めた後、どの部分を共通化するかを決めました。共通で使える要素を切り出す必要があったため、おのずとモデルに関心を持たないパーツである見た目のみの役割を担ったuiを切り出すことにしました。

アプリ側の構成
src
├── components
│   ├── functional モデルに関心を持たないかつ、見た目を伴わないコンポーネント
│   ├── model      モデルに関心を持つコンポーネント
│   └── ui         モデルに関心を持たない見た目だけを担当するコンポーネント

https://zenn.dev/yoshiko/articles/99f8047555f700

共通化の方法を決める

npm package で共通化を行うことにしました。

選定案

  • git submodule
    • 外部の git リポジトリを、自分の git リポジトリのサブディレクトリとして登録し、特定の commit を参照する仕組み
  • git subtree
    • 外部のgitリポジトリを自分のgitリポジトリのブランチとして取り込み、そのブランチを丸ごとサブディレクトリに配置する仕組み
  • npm package 👑
    • 皆様お馴染みの npm install hoge で導入可能なパッケージ

選定理由

普段ライブラリを導入するのと同様の手順で直感的に利用できるという点より npm package を選定しました。

git submodule や git subtree での運用場問題はありませんでしたが、慣れていないメンバーの学習コストを考慮すると npm install hoge で導入できる npm package が魅力的でした。一方、npm package を使うに当たってもライブラリを作るコストが高くなってしまうのではないかという懸念点は残りましたが、publish せずに git から直接インストールして利用することでより手元に近い環境で利用できる方針でカバーしていくことにしました。

yarn add git+ssh://git@github.com:xxx-inc/xxx-ui.git

https://docs.npmjs.com/cli/v9/commands/npm-install?v=true

作成

Next.jsで利用するのでReactが動く最低限の環境を用意すればOKで使う技術も至ってシンプルです。UIはChakraUIを導入していたので共通部分でもChakraUIを入れておく必要があります。

技術

  • React(TS)
  • ChakraUI
  • ESLint/Prettier
  • StoryBook
  • Rollup

構成

作る側の人がなるべく迷わないようにアプリ側の構成とつくりを揃えました。

├── .storybook
├── src
│   └── components 
│       └── ui  
├── stories

設定

パッケージの公開はしませんが、npm insallした際に node_modules 内にインストールされる一連の流れは同じです。
バンドラーには Rollup を利用しライブラリ作成と同様の手順で設定を行います。

package.json
  "name": "hoge-ui",
  "version": "0.0.1",
  "description": "UI modules",
  "main": "dist/index.js", // エントリーポイント
  "files": [ // パッケージに含めるファイル
    "dist"
  ],
  "scripts": {
    "prebuild": "rimraf dist", // ビルド前処理
    "build": "rollup", // ビルド処理
  },

色々端折ってますが詳細を知りたい方はライブラリを公開されている記事を参考にしてみてください。
https://zenn.dev/yuki0410/articles/74f80c4243919ea2a247-2

導入

パッケージが出来上がったらアプリ側で導入していきます。

yarn add git+ssh://git@github.com:xxx-inc/xxx-ui.git

node_modules配下にインストールされます。

作成したパッケージからコンポーネントをimportして利用する。

import { Button } from 'hoge-ui'

運用

導入後はアプリ側を実装するのと同時並行にUIコンポーネント側も実装します。
Reactが最低限動く環境となっているため、Storybookでコンポーネントをうつしながら作業となります。
コンポーネント実装後 git へ push した後、アプリ側でコミットIDを指定してアップグレードを行います。

yarn upgrade git+ssh://git@github.com:xxx-inc/xxx-ui.git#commitId

コミットIDを参照するためUIコンポーネント自体の運用ブランチは main のみとしました。
アプリ側でブランチごとに開発を分けていてもコミットIDを参照するため特に問題はありませんでしたが、複数名で作業する場合は main ブランチの最新化が必須です。

注意点

UIコンポーネントのリポジトリが private の場合権限の問題で CI や Amplify 側で正常にインストールすることができないので適宜設定が必要です。それぞれ Token を発行して対応を行いました。

CI
GITHUB_TOKEN を用いて設定
https://docs.github.com/ja/actions/security-guides/automatic-token-authentication

Amplify
GithubDeployKey を用いて設定
https://docs.github.com/ja/developers/overview/managing-deploy-keys
https://zenn.dev/micronn/articles/6e57d2175691b1

おまけ

一連の流れではあまり触れませんでしたが UIコンポーネントを作るにあたり ChakraUI を用いたのがかなり良かったのでおまけとして書いておきます。

カスタマイズ性に富んでいる ChakraUI ではラッパーコンポーネントを作らずとも theme を設定することである程度コンポーネントをカスタマイズすることができます。theme ではフォントサイズやカラースキーマ、ブレイクポイントまで様々な設定が可能です。
https://chakra-ui.com/docs/styled-system/customize-theme#customizing-global-styles

今回は3画面分のアプリを作らなくてはいけないものの、共通で使うデザインが統一されていたため theme ファイルも一緒に共通化することですべてのアプリで重複した theme ファイルを作成することなく実装することができました。

import { extendTheme } from '@chakra-ui/react'
import { themeExtensions } from 'hoge-ui'

export const theme = extendTheme(themeExtensions)

また、各画面で少しデザインを変えたいといった場合でもそれぞれの theme を設定するタイミングで上書きすることができるのでアプリごとにブレイクポイントを設定するといったことも可能です。

しっかりした土台の上でコンポーネントを実装していくことができるのでめちゃめちゃ便利でした!!

ふりかえり

  • 🤗 やはり共通化することで圧倒的効率良い開発ができる
  • 🤗 コミットIDを参照してくれるのでデプロイフロー不要で気軽に導入できる
  • 🤗 ライブラリ作る人の気持ちがちょっとわかるようになる
  • 😣 UIコンポーネントを更新した場合、アプリの数分 upgrade しなくてはいけなかったのが少々手間だった
  • 😣 パーツを import する際に名前空間がすべて一緒だったので、規模が大きくなってきたときにわかりずらくなるので対策が必要そう

さいごに

探り探りのところが多かったのですがかなり総合して良いことが多かったので今後も積極的に取り入れていきたいです!!!

Discussion