villioのフロントエンド利用技術とアーキテクチャ
こんにちは!
めろたんといいます!
前回は全体的に技術スタックの紹介をおこなったのですが、 今回は、Webフロントエンドで利用している技術とアーキテクチャに特化してを紹介したいと思います!
利用しているベースの技術
前回のブログでも書いているのですが、「Next.js」を使ってアプリケーションを構築していて、「TypeScript」で書いています。
ディレクトリ構成に関しては以下のようになっています。
└── frontend
├── components
│ ├── app
│ │ ├── model
│ │ │ ├── meeting
│ │ │ ├── report
│ │ │ └── ...
│ │ ├── page
│ │ └── global
│ └── lib
├── functions
├── hooks
├── models
├── pages
├── repositories
├── state
└── theme
各々ざっくり説明します。
Components
そのままですが、コンポーネントを配置しています。 配下に app と lib があり、さらに app 配下には global, model, page があります。
このあたりの構成については、2020年に立ち上げたWebフロントエンド構成の振り返り を参考にさせていただいています。
app にはTalentAmpでしか使用しないコンポーネントを配置しています。
global には、ページを横断するグローバル系のコンポーネントを配置しています。例えば、Header であったりアプリケーションのレイアウト等になります。
model には、model に関するコンポーネントを配置しています。例えば、アジェンダに関するコンポーネント等になります。
各画面で model に関するコンポーネントを利用したい等に役立ちます。
page には、各 model や global, lib のコンポーネントを組み合わせてページを構成したコンポーネントを配置します。
また対象のページ専用のコンポーネントについてはその対象のページのコンポーネントのディレクトリ配下に専用にコンポーネントを作って利用するようにしています。
lib には TalentAmp 以外でも使用できるようなコンポーネントを配置しています。
基本的には ChakraUI を利用した単純なコンポーネント(例えばボタンとか)を配置していたり、DataPicker 等を配置しています。
またコンポーネントを新たに作るときは、「Hygen」を利用して、テンプレートから作るようにしています。
これで必要な諸ファイルを自動でつくるようにしているので、コピペ等々を行わずにすみます。
各ディレクトリからimportできるファイルはeslintのルールで縛っており、libからはappはアクセスできない等を決めています。
functions
各コンポーネント等で利用する汎用的な関数を配置しています。
例えば、dateを受け取って任意の表記に変換するものや、日付順でソートするもの等を配置しています。
hooks
各コンポーネントで利用する汎用的なカスタム hooks を配置しています。
ログインユーザーの情報を取得する等の hooks があります。
models
フロントエンドで使用する model の型定義であったり、その model の操作等に関する関数が配置されています。
pages
これは Next.js のルーティングのためのディレクトリになります。
基本的には、components/app/page のコンポーネントを使うのみにしています。
repositories
API の通信等、外部からデータを取得したり更新等を行う処理を定義しています。
state
ここでは後述しますが、 recoil の atom と selector を定義しています。
現状多くはないのですが、ログインユーザーの情報を使うために使用しています。
theme
TalentAmp で使用するテーマを定義しています。
色であったり各種大きさ等を定義しています。
このあたりのディレクトリ構成に関しては ADR(Architecture Decision Records) としてドキュメントで残しているので、当時どういうモチベーションでこういう構成にしたのか等が残っていて良いです。
色々細かいルールや決まりごともあるのですが、ここでは一旦簡素に構造についてだけにとどめておきます。
API
API については、OpenAPI を使ってインターフェースを定義しているので、「openapi-generator」を使って TypeScript のコードをジェネレートしています。 型もいい感じに作られるので便利で良いです。
API を利用するときは「SWR」を使用しています。
サービスの性質上、お互いにアジェンダを書く等で一定リアルタイム性が必要な部分が存在しています。
そこで SWR の機能でポーリングをいい感じできる物があるので、それを利用することで簡単にですがリアルタイム性を担保しています。
詳しい SWR の使い方はここでは書きませんが、以下のようにすることで3秒毎にポーリングを行うようにしてくれます。(自動再検証 – SWR)
useSWR('hoge', () => fetchHoge(), { refreshInterval: 3000 })
状態管理
状態管理には 「Recoil」 を使っています。
とはいえ、多用しているわけでなく、基本的には SWR でサーバーから取ってきた情報をキャッシュしているため、あまり使っていません。
現状だと、ログインユーザーの情報を保持するために使用しています。
具体的には以下のように使用しています。(雑サンプルコードなので動くわけではありません)
// state/loginUser/atom.ts
export const loginUserState = atom<User | null>({
key: 'loginUser',
default: null
});
// components/app/global/LoginUserProvider.ts
export const LoginUserProvider = ({ children }) => {
const router = useRouter();
const [loginUser, setLoginUser] = useRecoilState(loginUserState);
useEffect(() => {
try {
setLoginUser(fetchLoginUser());
} catch {
router.replace('/login');
}
}, []);
if (loginUser === null) return <div>Loading...</div>
return children;
};
このような感じでログインユーザーを取得し、各ページで Atom からログインユーザーの情報を利用するようにしています。
多言語対応
「next-i18next」を利用して、日本語と英語に対応するようにしています。将来的に海外拠点での利用等も考えていたというのもあり、いずれ必要というのはあったため、途中から入れるより最初からある前提で作ったほうが良いだろう。ということで導入しました。
開発当初は日本語のみでつくっていたのですが、英語が必要になったタイミングが近々であったため、この判断はよかったなと感じています。
コンポーネントカタログ
これについては特段特別なことはしていないのですが、「Storybook」を利用して各コンポーネントを一覧できるようにしています。 各コンポーネントで API の通信が必要なものがあるのですが、それについては「MSW」を利用しています。
openapi.yaml にレスポンス例を記述しておき、そこからいい感じにパースし使用しています。
テスト
「Jest」を利用しています。
特別なことをしているわけではないのですが、アクセシビリティチェックをしていて「jest-axe」を利用しています。
完璧な確認ができるわけではないものの一定のチェックは事前にできるため便利です。
実際にラベルのつけ忘れ等を事前に気づけたこともあったのでとても便利だと感じています。
このアクセシビリティチェックのテストは、先述したテンプレートから生成した際に自動で作られるテストになっているため、忘れることなくチェックを行えるようにしています。
まとめ
以上が、TalentAmp のフロントエンドで利用している技術やアーキテクチャになります。
すごく特別なことをしているわけではないのですが、まだまだ走り始めのプロダクトで完璧ではないもののテストもそれなりにあり、各種扱いやすいように作っているのは自分たちのことなのですがすごいなと感じています。
今回はざっくり概要という感じではあるので、実際どんな感じなのか、現状で問題がありそう・あるところはどこなのか、他にも実際にどう開発を進めているのか等々、興味がある方は応募してください!話しましょう!
より詳しくvillioについて知りたい方はこちら