🌟

2020年に立ち上げたWebフロントエンド構成の振り返り

2021/09/27に公開
6

こんにちは、よしこです。
株式会社ナレッジワーク というスタートアップで、2020年4月の創業時から一人目のフロントエンドエンジニアをしています。

初期に考えて組み上げたスタックで1年半ほど開発・運用してみて、なかなか快適に日々開発ができているので

  • 新規開発のプロダクト立ち上げ時にどのようにフロントエンドを構築したのか?
  • 立ち上げから1年以上開発・運用を続けてきた今、それらの選択はどうだったのか?

を記事にして振り返り、公開したいなと思いました。

(プロダクトの内容はステルスで進めていてあまり対外的な発信ができないので、かわりに技術的なところはどんどんオープンにしていきたいなという気持ちがあります)

いろいろな項目ごとに振り返りたいので、この記事は各項目を横断するindexとして項目ごとの概要を簡単に説明し、深堀りは項目ごとに追って詳細な記事を書いていく予定です!

前提

プロダクトとしての要件

  • ツールとしての側面が強いBtoB SaaS
  • アプリケーション部分に関してはログインが必要で、SEOやページキャッシュの必要性はない
  • スマートフォン対応は視野には入れておきたいがメインで想定する端末はPC
  • IEはサポート外

フロントエンドチームの規模

  • 現在フルタイムのフロントエンドエンジニアは自分一人
  • フロントエンドの開発に携わってくれているメンバーは他に4人ほど(副業の方や別職種の社員など)

ベースの技術

  • TypeScript、React、Next.jsを採用
  • TypeScriptは型が欲しかったので必須
  • Reactは、正直メジャーなFWなら大抵どれでも要件を満たしたSPAは作れば作れると思っているので、好みと慣れの側面が一番強い
  • Next.jsはルーティングとビルドやパフォーマンス面の支援が欲しかったのであわせて採用。要件にSSRの必要性が薄いので、今後静的なホスティングもやろうと思えばできるようにSSR関連の機能やAPI機能は使っていない

SPA アーキテクチャ

Stateのアーキテクチャ

  • アプリケーションに存在するStateを以下の3種類に分類
    • Server Data Cache, Global State, Local State
  • Server Data Cacheはデータ取得ライブラリのSWRを通して読み書き
  • Global StateはRecoilを通して読み書き
    • single stateではなく役割ごとに独立したstateを作る
  • Local StateはReactのuseStateのこと

詳細記事:
https://zenn.dev/yoshiko/articles/607ec0c9b0408d

Applicationのアーキテクチャ

  • 存在する主なレイヤーはComponent, Usecase, Repository, Store
  • 必要最小限のかなりシンプルなものにしており、特定のアーキテクチャに基づいたものではない。fluxでもない。
  • データフローが単方向になるようにはしている。Component -> Usecase -> Store -> Component というかなり単純な形
    • RepositoryはStoreに対するRepositoryではなくAPI serverに対するRepositoryなのでデータフローには関わらない。Usecase内で使われるだけ
  • hooks APIと純粋関数の境界をどこにどうやって置くか?を色々考えて工夫した
    • UsecaseとRepositoryはテストしやすいよう引数で依存を注入する純粋な関数にしておき、アプリケーション内で使うときにはhooksでラップしたものを使っている(hooksラッパー内で他のhooksから依存を集めてきて注入できる)

詳細記事:
https://zenn.dev/yoshiko/articles/91a3dd575f99a2

Componentのディレクトリ構成

  • アプリケーションに存在するComponentを以下の4種類に分類してディレクトリを分ける
    • src/components/page
    • src/components/model
    • src/components/ui
    • src/components/functional
  • pageは1つのページを表すComponent
  • modelは何らかのmodel(user等)に関心を持つComponent
    • modelの種類ごとにディレクトリを切って、同じmodelに関心のあるComponentをその下に並列に並べている
      • src/components/model/user/UserAvatar
      • src/components/model/user/UserPicker みたいな
  • uiはmodelに関心のないUI Component
    • checkboxとかbuttonとか。layout関連もUIではないけど今はここに入れている
  • functionalはmodelに関心がなく、かつviewを持たず振る舞いだけを持つComponent
    • hooks APIではなくComponentの形式で提供したいものはここ
    • ErrorBoundaryとか、refを伴うhooksをラップしたものとか

詳細記事:
https://zenn.dev/yoshiko/articles/99f8047555f700

APIとの繋ぎ込み

  • Protocol Buffersを使用したスキーマ駆動開発
  • protoファイルからGo/TypeScriptの型定義ファイルを生成してそれぞれ使うことで、通信部分も型安全
  • 型定義ファイルの生成はCIで自動化
  • 事前にスキーマが決まっていることで、APIが未実装でもfrontend内でAPIの型に沿ったmockを作って実装を進めることができる
  • 実際の通信はfrontendとserverの間にExtensible Service Proxy(ESP)が立っており、frontend <-> ESP間はREST、ESP <-> server間はgRPC

詳細記事: Coming soon

スタイリング

スタイリング

  • Pure CSSに一番近い.cssファイルを運用できるCSS Modulesを採用
    • 読み込み順序の問題に対しては詳細度の工夫で対応
  • 変数はネイティブのCustom Propertiesを使用
  • 基本的にstyleはUI Componentに集約し、pageやmodelのComponentではUIを組み合わせることでデザインを実装できるのが理想形
  • styleを伴うUIフレームワークやライブラリは基本的に使わない

詳細記事: Coming soon

デザインとの協調

  • デザインはFigmaでおこなわれている
  • src/components/uiに存在するComponentとFigmaに存在するデザインコンポーネントをなるべく1:1に対応させていく
  • 1:1対応推進のため、frontendの実装済みUI Componentの一覧は常に最新の状態のstorybookを社内にホスティングしてデザインチームがいつでも見られるようにしておく
    • 逆にFigmaのデザインコンポーネントもエンジニアがいつでも見られるようにしておく
  • TypographyのバリエーションもComponentとして管理

詳細記事: Coming soon

開発補助

テスト

  • 今回しているテストは Unit Test, Visual Regression Test, E2E Test の3つ
  • Unit Testはjest。対象はUsecase、Repository、entityごとの関数群など。PRごとにCIで回す
  • VRTはreg-suit。対象はComponentをスタイルごと。PRごとにCIで回す
  • E2E Testはplaywright。対象はdev環境のE2E用テナントで、1日1回実行される。stg環境に対しての実行もできる。大半のmodelのCRUDをチェックしている

詳細記事: Coming soon

linter/formatter

  • eslint, stylelint, prettierを設定
  • eslintは社内でディレクトリ間のimportルールを決められるカスタムルールを実装・運用しており、好ましくない方向の依存に対して自動でエラーを出せる
    • たとえばcomponents/uiがmodelに依存するのはNGとか
  • .vscode もリポジトリで共有して推奨設定はある程度効くようにしてある
    • ここは好みも強そうなので、チームメンバーの意見を尊重。今の所皆快適そう
    • editor.codeActionsOnSavesource.fixAll.eslintsource.addMissingImports をかけるのがとても快適。
    • 特に自動importとimport順序ルールのautofixを組み合わせたことでファイルの上部をいじりに行くことがなくなった

詳細記事:
https://zenn.dev/yoshiko/articles/0994f518015c04

mockの活用

  • model(User, Category...等)ごとに必ずモックデータを手動で定義するようにしている
  • すべてのmodelにモックデータがあるので、それをAPIのスキーマ定義通りに返すオブジェクトを作ることでAPI Serverのモックが作れる
  • それを利用して、API通信先モックに置き換えたdebug mode(API通信を伴わない起動モード)を実現している。serverの実装前はこちらを使うことでfrontendに閉じて機能開発ができる
  • モックデータはユニットテストやstorybookでも活用している

詳細記事: Coming soon

scaffolderの活用

  • model, page, component作成用にそれぞれ scaffdog を使ったscaffolderを用意している
  • それぞれの作成時に必ずscaffolderを利用することで、フォーマットを啓蒙しなくてもチーム全体で自然と統一されたフォーマットを運用することができる

詳細記事: Coming soon

依存ライブラリの継続的アップデート

  • 月1でrenovateを使って依存ライブラリをアップデートしている
  • 1年ぐらいは週1だったが、頻繁すぎてリリース直後のバグをひいてしまうことがたびたびあったので月1にしてみた。が、それはそれで溜まりすぎる感じもあるので頻度はまだ試行錯誤中
  • dependenciesとdevDependenciesでPRを分け、devDependenciesはCIが通ればauto-mergeする設定

詳細記事: Coming soon

その他

ディレクトリ構成

src以下をざっくり紹介

  • components : Componentアーキテクチャの項目で紹介済み
  • usecases repositories : Applicationアーキテクチャの項目で紹介済み
  • globalStates : Stateアーキテクチャの項目で紹介済み
  • mocks : mockの活用の項目で紹介済み
  • generated : スキーマ定義から自動生成されるファイル
  • libs : node_modulesのライブラリを直接使わずにラップしたい場合ここに
  • models : modelごとの型定義や関数郡など。modelは振る舞いを持たないデータ構造体にしているので、model methodにあたる振る舞いは別途参照透過な関数群として用意している
  • pages : Next.jsのルーティング用。ここにあるファイルには実装は書かず、対応するcomponents/page以下のComponentをimportしてexportするのみ
  • styles : reset.cssやbase.css、variables.cssなど
  • hooks : 共通で使いたいhook

詳細記事: Coming soon

ホスティング・デプロイ

  • 立ち上げ時にインフラメンバーがいなかったこともあり、デプロイまわりを自分一人でも構築できそうだったVercelを利用
    • toBなのでトラフィックの増加は事前に予想がつきやすく、Serverless Functionsも使わないのでProプランの上限に達するにはだいぶ猶予がありそうと判断(実際まだ余裕)
  • monorepoだとGitHub連携に色々と障壁があり、現在は連携はせずにCI内でVercel CLIを叩いている

詳細記事: Coming soon


以上!

Coming soonになってる各項目の詳細記事は今後こつこつ書いてリンクしていきたいと思います。
(上から順に書くと決めてるわけでもないので、もし「これ特に知りたい」のような反応があればそれを先に書くかもしれません)

あと技術的なところ以外だと、たとえば採用面ではスキル選考をする立場としての練度を上げるために以下のような募集をしてみたりと、色々チャレンジ中です!

https://yoshiko.hatenablog.jp/entry/2021/09/08/131119

この記事は目次なので各項目の課題感にはあまり触れませんでしたが、まだまだ足りてないところや手が回ってないところもあるので、もっとチーム拡大してやれること増やしていきたいなーと思っています。

なので最後にちょっとだけ宣伝しますが、この記事の内容や、またはステルスで進めているプロダクトってどんな内容なの?などなどどんなことでも興味を持ってくださった方がいたらお気軽に Twitter DM ください!(フォローしなくても送れます)

Discussion

kanon_codekanon_code

とてもためになりました!
ディレクトリ構成やComponentアーキテクチャの部分を更に知りたいなと思いました!

よしこよしこ

ありがとうございます!
2つとも書いて公開できましたのでよろしければぜひ!

massomasso

とても参考になりましたので、バッジを贈らせていただきました!
いろいろなコンテクストの上で、試行錯誤された感じが伝わってきて、とても読み応えがありました。

よしこよしこ

ありがとうございます!バッジもコメントも嬉しいです🙌

kkoislandkkoisland

とてもわかりやすく参考になりました。スタイリングについてもう少し知りたいです。具体的には、CSSのフォルダ構成についてお聞きしたいです。また、なぜUIフレームワークやライブラリを使用しないのでしょうか。自前のライブラリを作っているのですか。

よしこよしこ

コメントありがとうございます!
UI Componentは自前で揃えています!
UI系のライブラリは使っているものもありますが、CSSを伴わないHeadlessなものだけ使うというポリシーにしています!
そのあたりも含め、スタイリングまわりの記事検討してみますね!🙆‍♀️