📗前提
- 「今から新しく Vue.js のフロントエンドアプリを立ち上げるならこんな構成/戦略を取る」 というテーマで書きました。
- 作るアプリケーションは、toB (企業向け) のものを意識しています。
- 要件次第では当然取るべき戦略も変わるので、あくまで一例として考えてください。
🔨セットアップ
インストール
- Vue.js の公式に従う。
- 自分のテンプレートを持っておく方法もあるが、フロントエンドは特に技術の移り変わりが早いためお勧めしない。
npm init vue@latest
✔ Project name: … <your-project-name>
✔ Add TypeScript? … Yes
✔ Add JSX Support? … No
✔ Add Vue Router for Single Page Application development? … Yes
✔ Add Pinia for state management? … No
✔ Add Vitest for Unit testing? … Yes
✔ Add Cypress for both Unit and End-to-End testing? … No
✔ Add ESLint for code quality? … Yes
✔ Add Prettier for code formatting? … No
採用✅ / 非採用❌ の理由
✅TypeScript
- 型安全に開発したいので採用する。
- コードが壊れたときにすぐ気づける。
- 仕様を型として表現できるのでドキュメントの役割も持つ。
❌JSX
- vue ファイルよりも表現力が高いが、必要になる場面は限定的。
- 必要になった時に採用すればいいと考え非採用にした。
✅Vue Router
- ルーターライブラリのデファクトスタンダード。
- これ以外使ったことない。
- 使っていて致命的に困ったことがない。
- ただし Storybook やテストでの扱いに注意。
❌Pinia
- 後述するが、状態管理には Vue の標準 API のみを使う方針のため。
✅Vitest
- 単体テストのフレームワークとして採用する。テストについての詳細は後述する。
- アプリケーションと同じプロセスでテストを素早く実行できる点が Jest などと比べて優位性がある。
❌Cypress
- 後述するが、E2E は積極的に行わない方針なので非採用とする。
✅ESLint
- フォーマッタ、静的コード分析ツールとして採用する。詳細は後述する。
❌Prettier
- ESLint とルールが衝突すると面倒。
- フォーマットルールの細かな調整ができない。シンプルではあるが、痒いところに手が届かない事が多い。
Vite
-
npm init vue@latest
標準でインストールされる。 - Webpack よりも圧倒的に早くフィードバックを得られ、開発が早い。
- 開発コミュニティーも活発。
- Storybook も Vite で開発可能になった。
- 現時点では、特に理由がなければ Vite を使えば OK。
Storybook
- 開発 / テスト / ドキュメンテーション のツールとして利用する
- Storybook 7 が公式リリースされたので、どうしても使いたいプラグインが 7 に対応していない、等の理由がない限りは 7 を利用する。
VSCode 拡張
- Volar は必須
- TailwindCSS を使うなら Tailwind CSS IntelliSense を使う
- Storybook を使うなら vue-inline-template などを使って、story ファイルの template をハイライトする。
🏃♀️開発の進め方
タスク化
- 画面仕様書から実装やテストで保証すべき点を洗い出す。
- デザインからコンポーネント設計をする。
- 2 で切り出したコンポーネントごとに 1 で洗い出した保証すべき点を分配する。
見積もり
コンポーネントやタスクごとに 1h
2h
4h
8h
で見積もった後、必要に応じて加算していく。
下記に見積もりの例を示す。
見積もり時間 | 例 |
---|---|
1h | ・簡単なレイアウトを責務とするコンポーネント ・確認ダイアログのようなコンポーネント ・既存コンポーネントの軽微な修正 |
2h | ・あるモデルの情報を表示するコンポーネント |
4h | ・あるモデルの情報を作成/編集するコンポーネント ・あるモデルの一覧を表示するテーブルコンポーネント ・ボタンやインプットなどのシンプルな基本 UI コンポーネント |
8h | ・コンポーネント同士をつなぎこむページコンポーネント ・ドロップダウンやテーブルなどの複雑な基本 UI コンポーネント |
観点 | 加算工数 |
---|---|
仕様で不明確な部分がある | x1.5 |
状態を持つ | +1h |
スタイルが複雑 | +2h |
プロジェクトで実績がない技術や仕組みを利用する | +1-8h |
ストアを利用する | +1h |
ローカルストレージやクエリパラメータと状態を同期する | +2h |
インタラクションテスト | +0.5h / 1 ケース |
通常以外の Story がある | +0.5h / 1 story |
実装
- コンポーネントと Story のファイルを作成する。ひな形から作れるようにしておくと良い。
- Storybook を見ながらコーディングしていく。この時一覧性のあるストーリーを作ることで、開発効率を上げ、同時に VRT として利用できる。下記の記事にまとめた。
🖊️コーディングスタイル
繰り返し作業を減らす
<script setup>
を利用する
- コンポーネントを書くときに必要な
defineComponent
などの決まり文句を省略できるようにするシンタックスシュガー。 - 記述量がかなり減り開発が早くなる。
-
inheritAttrs
など一部のオプションは<scrip setup>
に対応していない。 - 必要になったタイミングで、同じファイル内に
<script>
を共存させれば OK。 - 初心者にとってはよりブラックボックスになるので注意する。
コンポーネントのインポートを不要にする
- unplugin-vue-components を使用する。
- コンポーネントの import が不要になる。
- 型も効いて快適に開発できる。
- 下記の記事で使い方をまとめました。
意味をコードに落とし込むことを意識する
- とにかく可読性を重視する。
- コメントが無くてもすらすら読めるような状態が理想。
- 実現したいこととコードが一致するように抽象を作る。
Linter 設定
- 細かなコード規約は意識しなくていいように Linter のルール設定に任せる。
- ESLint と Prettier はしばしばルールが衝突するので併用しない。
- より細かな設定ができる ESLint を採用する。
- 保存時に自動整形されるようにエディタを設定する
- ESLint の設定値は
@antfu/eslint-config
を元に、好みやチームに合わせて必要なルールを上書きする。
🧩コンポーネント管理
詳しくは下記にまとめた。
下記に要点を示す。
ディレクトリ構成
- 各画面のトップレベルのコンポーネントは
pages/
以下に置く。 - それ以外のコンポーネントは全て
components/
以下にフラットに置く。
命名規則
-
ArticleTextColorPallet
のように 属性 + 要素 という形式で命名する。
分割の仕方
- Core / Page / 一般 に分ける。
- ボタンやテキスト入力などのプロジェクト横断で利用するコアコンポーネントは UI ライブラリに実装する。
- 原則として1コンポーネント1責務。ただし、近い関心事で、切り出しのメリットが少ない場合は同居を許す。
- 早すぎる抽象化を避ける。同じ実装が 3 回登場したときに抽象化を検討すればよい。
- 切り出すことでテストがしやすくなる場合は切り出す。
🎨スタイリング
-
原則 TailwindCSS のみでスタイリングする。生の CSS はどうしても必要な時以外書かない。
- どの要素にどのスタイルがつけられているかが明確になる。
- HTML と CSS を行ったり来たりしなくて済む。
- 色や間隔などをプロジェクトである程度統一できる。
- Windi CCS や UnoCSS あたりは今後使ってみたい。
📱UI ライブラリ
- OSS の UI ライブラリを利用するのであれば Vuetify あたりを検討してみる。
- コアな UI パーツを自作する方針の場合でも、Headless UI (スタイルを持たない UI ライブラリ) を利用する。理由は下記。
- コアコンポーネントの実装工数を短縮できる。
- Prop や Slot の設計が洗練されている。
- セマンティックな実装がされているため、ユーザビリティが高い。
💡状態管理
- Pinia などの状態管理ライブラリは原則使わない。
- アプリケーション側で複雑なキャッシュなどの機構を実装する必要がある時に状態管理ライブラリの採用を検討する。
- 過去に Vuex を導入したところ、API を叩く部分の処理やダイアログの管理など、あらゆる箇所から利用され、コードが管理できなくなってしまった経験から。
- store を持ちたいときは、store を管理する hook を使って provide / inject でコンポーネント間でデータの受け渡しを行う。
- provide / inject を使うことで、状態管理ライブラリが提供するグローバルな store よりも、データを使える範囲が明確になるため。
このセクションの内容とほぼ同じ内容の記事を見た記憶があるのですが見つけらませんでした。
誰か知っている方がいたら教えて欲しいです。
🧪テスト
プロジェクト全体でのテスト戦略
- E2E はコストが高いので最小限に抑える。
- ただし、Autify のように AI を活用してコストを抑えられる仕組みの導入は検討の余地があると思っている。
- API / フロント それぞれ単体で完結するテストを中心に書く。
- API スキーマが正しいことを保証するテストを書く
フロントエンドのテスト戦略
単体テスト
- 見た目を伴わないロジックのテストのことを指す。
- 実行速度が速く、デファクトスタンダードになりつつある Vitest を採用する。
VRT
- Chromatic を利用してスナップショットテストを継続的に行う
- コンポーネント開発時は、そのコンポーネントのあらゆる状態を表示するストーリーを書き、それをそのまま VRT として活用する。
インタラクションテスト
- フロントエンドのトップレベルのテストとして Storybook の インタラクションテスト を書く。
- 原則 Page レベルのコンポーネントの Story にインタラクションテストを記述する。
- ユーザのアクションに対して、何が起こるか?をテストする。
- 例えば、「ユーザがフォームに適切に値を入力してクリックすると、API リクエストが行われ、保存成功のトーストが表示される。」 のようなもの。
- 下記の記事に頻出のテストコードをまとめた。
CI
下記を CI で行うように設定する。
- 型チェック
- Linter
- 単体テスト
- Chromatic Publish
Chromatic を Publish すると、Story が壊れていないかをチェックするスモークテストとインタラクションテストが実行されるので、実質 CI として利用できる。
スキーマ保証
- OpenAPI を使っている場合、openapi-typescript を使って型定義を自動生成する。
- バックエンドも TypeScript で書いている場合は、tRPC を利用する。
📚セマンティクス
- 作るアプリケーションの要件に応じて、どの程度セマンティックな実装をするかの方針を立てる。
- 私が経験の多い toB 領域の場合、キーボードだけで操作が完結できることを目標にセマンティックな実装をするのが良さそうだと思っている。それ以上は要件や工数次第で決める。
📦ライブラリ
選定基準
下記の観点で複数の候補を比較して決定する
- 実現したいことができるか
- そのライブラリを使うことでどれくらい工数を削減できるか
- 自分たちのアプリケーションの動きやパフォーマンスを壊さないか
- よくメンテナンスされているか
- コミュニティが活発か
- ダウンロード数が多いか
よく使うライブラリ
ts-pattern
- TypeScript でパターンマッチングをするためのライブラリ。
- 複雑な条件分岐処理を型安全に行うことが出来る。
Lodash
- 配列やオブジェクトの操作するための関数群を利用するためのライブラリ。
- chaining を利用して複雑な処理を簡潔に表現できる。
- 全ての関数を含めると 500kB 程になってしまうので注意。
zod
- スキーマファーストなバリデーションライブラリ。
- API にリクエストを送る前のバリデーションに利用する。
- tRPC のスキーマ定義にも使える。
Day.js
- 軽い
- 使いやすいインターフェース
- 関数型っぽく書ける
おわりに
私のフロントエンド戦略をざっとまとめてみました。フロントエンドは移り変わりが早いので、半年後には変わっている部分もあるでしょう。少しでも誰かの参考になれば幸いです。他の人の戦略も見てみたいですね。