Slide 1

Slide 1 text

マイグレーションコード自作して File-Based Routing に自動移行!! ~250 ページの歴史的経緯を添えて~ 株式会社WinTicket 加藤 零 2024/08/27 Muddy Web #9

Slide 2

Slide 2 text

加藤 零 - kato, rei U 2023年 株式会社サイバーエージェント新卒入C U 株式会社 WinTicket 所 U # Web Speed Hackathon 2024 作問者 自己紹介 @cut0_ @cut0

Slide 3

Slide 3 text

目次 01 WINTICKET について 02 TanStack Router への移行背景 03 ページの移行 04 Hooks の移行 05 移行してみて 06 おわりに

Slide 4

Slide 4 text

目次 01 WINTICKET について 02 TanStack Router への移行背景 03 ページの移行 04 Hooks の移行 05 移行してみて 06 おわりに

Slide 5

Slide 5 text

% 公営競技ネット投票サービ % 競輪/オートレー % 2019年4月リリー % 市場シェアNo.1 #React #Fastly #Go #Kubernetes #Flutter #Cloud Run #Google Cloud #TypeScript #TanStack Query WINTICKET について

Slide 6

Slide 6 text

WINTICKET Web チームについて G チームメンバー 5 h G サービス本体の開発 + Web に関連する社内ツールの開W G WINTICKE` G 管理画# G ダッシュボー G その他内製ツール

Slide 7

Slide 7 text

WINTICKET Web チームについて $ チームメンバー 5 E $ $ WINTICKE3 $ $ ダッシュボー# $ その他内製ツール サービス本体の開発 + Web に関連する社内ツールの開a 管理画I →今回話す対象

Slide 8

Slide 8 text

目次 01 WINTICKET について 02 TanStack Router への移行背景 03 ページの移行 04 Hooks の移行 05 移行してみて 06 おわりに

Slide 9

Slide 9 text

TanStack Router 移行背景 ) 移行方針$ ) 移行過程での障害と解決策 話すこと ) 詳細なマイグレーションコードの解説 話さないこと

Slide 10

Slide 10 text

TanStack Router 移行背景 ‚ 管理面はクラシックな SPA で構成されていて SSR を行っていなd ‚ GCS に HTML + JS + CSS をホスティングするシンプルな構` ‚ ルーティングライブラリの高度な機能を必要としなd ‚ TanStack Router も最低限のルーティング機能だけを利用 前提

Slide 11

Slide 11 text

TanStack Router 移行背景 C React Router v5 を利G C 250 ページに及ぶルーティングを1つのファイルで管理d C RoleRoute, ProtectedRoute の混 C 違いについては後述 障壁

Slide 12

Slide 12 text

TanStack Router 移行背景 ( params に型がつかな@ ( History API を直接触っており、 ページパスに型がつかない 課題

Slide 13

Slide 13 text

歴史的経緯① ~RoleRoute と ProtectedRoute~ 各社員の権限を基に、適切な画面の閲覧権と操作権を管理する必要がある 管理画面の規模拡大に伴い、より詳細な権限の管理が必要になった WinTicket では、従来のロールからより詳細な権限管理用のロール(新ロール) に移行中 前提 現在

Slide 14

Slide 14 text

歴史的経緯① ~RoleRoute と ProtectedRoute~ ProtectedRoutF X より詳細な権限に基づいてページの閲覧権を管理するラッパーコンポーネント RoleRoutF X 旧ロールに基づいてページの閲覧権を管理するラッパーコンポーネント

Slide 15

Slide 15 text

TanStack Router 移行背景 f ルーティング管理ファイルの肥大% f ロール管理の複雑% f ルーティングに関連するもろもろの型が安全でな2 f そもそも React Router を v6 に上げられていない 課題 TanStack Router への移行 解決策

Slide 16

Slide 16 text

TanStack Router について ~ルーティング~ TanStack Router は下記の二種類のルーティング手段を提供している File-Based Routind x ディレクトリ・ファイルの構造に応じてルーティングルールを設“ x src/pages/hoge/$fuda/index.tsx -> /hoge/:fuga にマッ› x src/pages/hoge/$fuda/-Example.tsx -> - プレフィックスはルーティングに含まれなv x 他にも様々なルールがある ( ) 参考 Code Based Routind x 以前の管理面と同様にコードにルーティングルールを記載する

Slide 17

Slide 17 text

TanStack Router について ~ルーティング~ TanStack Router は下記の二種類のルーティング手段を提供している File-Based Routind x ディレクトリ・ファイルの構造に応じてルーティングルールを設“ x src/pages/hoge/$fuga/index.tsx -> /hoge/:fuga にマッ› x src/pages/hoge/$fuga/-Example.tsx -> - プレフィックスはルーティングに含まれなv x 他にも様々なルールがある ( ) 参考 Code Based Routind x 以前の管理面と同様にコードにルーティングルールを記載する →ルーティングファイル肥大化問題を解決するため採用

Slide 18

Slide 18 text

TanStack Router について ~型周り~ TanStack Router は routeTree.gen.ts ファイルを自動生成する ↓のようにページコンポーネントとディレクトリ、パス情報を集約したファイル

Slide 19

Slide 19 text

TanStack Router について ~型周り~ useParams, useNavigate, useSearch 等よく使う Hook に型が通る →id に型が通る →パスに型が通る

Slide 20

Slide 20 text

目次 01 WINTICKET について 02 TanStack Router への移行背景 03 ページの移行 04 Hooks の移行 05 移行してみて 06 おわりに

Slide 21

Slide 21 text

ページの移行 方針 既存のルーティング用のコンポーネントを読み込む D path プロパティを読み、同等のディレクトリを作成 t component プロパティの定義をたどり、2 で作成し たディレクトリにページコンポーネントを移動

Slide 22

Slide 22 text

ページの移行 方針 既存のルーティング用のコンポーネントを読み込む D path プロパティを読み、同等のディレクトリを作成 t component プロパティの定義をたどり、2 で作成し たディレクトリにページコンポーネントを移動

Slide 23

Slide 23 text

障壁① ~RoleRoute と ProtectedRoute の混在~ RoleRoute, ProtectedRoute が同一のインター フェースで実装されていないため解析が煩わしい ProtectedRoute の実装を拡張し、RoleRoute を廃止 障壁 解決策

Slide 24

Slide 24 text

ページの移行 方針 既存のルーティング用のコンポーネントを読み込む D path プロパティを読み、同等のディレクトリを作成 t component プロパティの定義をたどり、2 で作成し たディレクトリにページコンポーネントを移動

Slide 25

Slide 25 text

ページの移行 src/pages-v2/campaigns/$campaignId ディレクトリを作成 src/pages-v2/admins ディレクトリを作成 src/pages-v2/admins/new ディレクトリを作成 src/pages-v2/campaigns ディレクトリを作成 ※pages-v2 ディレクトリは TanStack Router の File-Based Routing 用ディレクトリ

Slide 26

Slide 26 text

ページの移行 方針 既存のルーティング用のコンポーネントを読み込む D path プロパティを読み、同等のディレクトリを作成 t component プロパティの定義をたどり、2 で作成し たディレクトリにページコンポーネントを移動

Slide 27

Slide 27 text

ページの移行 import 文をたどり、ページコンポーネントのパスを算出 各ページコンポーネントを pages-v2 ディレクトリに移動する

Slide 28

Slide 28 text

AST の解析について AST の解析には jscodeshift を利用 p JavaScript の codemod、TypeScript でも利用可 p 左図のように AST を見ながらコーディングするのがv p 類似のライブラリでは ts-morph が上げられa p 管理面リポジトリでは既に jscodeshift の環境が整ってい たため、今回は jscodeshift を利用 p https://github.com/facebook/jscodeshif‹

Slide 29

Slide 29 text

ページの移行 方針 既存のルーティング用のコンポーネントを読み込む D path プロパティを読み、同等のディレクトリを作成 t component プロパティの定義をたどり、2 で作成したディレクトリに ページコンポーネントを移動 1 ~ 3 の手順を操作するためのファイルを作成

Slide 30

Slide 30 text

ページの移行 url ページの URL を示す isUnstated 後述 filePath 移行後のファイルパス beforeDepth, afterDepth 移行前移行後のルートからの深さ componentPath 移行前のファイルパス

Slide 31

Slide 31 text

ページの移行 ページの実態は同一階層の コンポーネントが持つ 各種ロールのハンドリングは ここでおこなう

Slide 32

Slide 32 text

障壁② ~3世代に渡る状態管理ライブラリの混在~ WinTicket 管理面では3種類の状態管理ライブラリが混在している unstate‹ ƒ Class Component 時代の状態管理ライブラ— ƒ 一番の障壁 Recoi‡ ƒ Hook ベースの状態管理ライブラリ TanStack Quer– ƒ 非同期状態の管理を得意とするライブラ— ƒ 最近 WINTICKET 本体は Redux から TanStack Query に移行した (参考) 障壁

Slide 33

Slide 33 text

unstated について クラスコンポーネント時代の状態管理ライブラリ E コンテナ → コンポーネントにロジックとステートを注入することで状態を管理 E WinTicket では今回の移行とは別枠で黙々と移行するプロジェクトが走っている E issue ページ自体が Close しており、メンテナンスされていない https://github.com/jamiebuilds/unstated

Slide 34

Slide 34 text

障壁② ~3世代に渡る状態管理ライブラリの混在~ その他 unstated ページ側にロジックとステートを注入

Slide 35

Slide 35 text

障壁② ~3世代に渡る状態管理ライブラリの混在~ ページの実態は同一階層の コンポーネントが持つ 各種ロールのハンドリングは ここでおこなう

Slide 36

Slide 36 text

障壁② ~3世代に渡る状態管理ライブラリの混在~ ページの実態は同一階層の コンポーネントが持つ 各種ロールのハンドリングは ここでおこなう コンテナからページにステートとロジックを 注入するための層が必要

Slide 37

Slide 37 text

障壁② ~3世代に渡る状態管理ライブラリの混在~ その他 unstated /$campaignId ├─ index.tsx : ルーティング用のコンポーネント └─ -CampaignDetail.tsx : ページの実態 ├─ -Injector.tsx : ロジックとステートを注入する層 /$campaignId ├─ index.tsx : ルーティング用のコンポーネント └─ -CampaignDetail.tsx : ページの実態

Slide 38

Slide 38 text

目次 01 WINTICKET について 02 TanStack Router への移行背景 03 ページの移行 04 ルーティング処理の移行 05 移行してみて 06 おわりに

Slide 39

Slide 39 text

ルーティング処理の移行 前提 C react-router v5 が提供する Route コンポーネントを利用していた C Route コンポーネントは対象に RouteComponentProps で定義された型を インジェクトすh C ※v6 では非推奨 C RouteComponentProps の中でも、history, location, match のみが利用されていw C history : History オブジェクト。主に history.push でのページ遷移に利{ C location : History API の location インターフェーj C match : パスパラメータを取得するために利用

Slide 40

Slide 40 text

ルーティング処理の移行 要は 下記の変換ができればよい

Slide 41

Slide 41 text

ルーティング処理の移行 方針 8V location, history, match それぞれ Props で 展開されている → コンポーネント内で Hook を利用 rV match.params を useParams に書き換f € from には前述の route-info.json を利用 uV location.search を location.searchStr に 書き換え

Slide 42

Slide 42 text

ルーティング処理の移行 方針 8V location, history, match それぞれ Props で 展開されている → コンポーネント内で Hook を利用 rV match.params を useParams に書き換f € from には前述の route-info.json を利用 uV location.search を location.searchStr に 書き換え

Slide 43

Slide 43 text

ルーティング処理の移行 方針 8V location, history, match それぞれ Props で 展開されている → コンポーネント内で Hook を利用 rV match.params を useParams に書き換f € from には前述の route-info.json を利用 uV location.search を location.searchStr に 書き換え

Slide 44

Slide 44 text

ルーティング処理の移行 方針 8V location, history, match それぞれ Props で 展開されている → コンポーネント内で Hook を利用 rV match.params を useParams に書き換f € from には前述の route-info.json を利用 uV location.search を location.searchStr に 書き換え

Slide 45

Slide 45 text

ルーティング処理の移行 方針 8V location, history, match それぞれ Props で 展開されている → コンポーネント内で Hook を利用 rV match.params を useParams に書き換f € from には前述の route-info.json を利用 uV location.search を location.searchStr に 書き換え もちろんこれは既存実装が統一されている場合の話です 実際は AST の変換をしやすいように 事前にコードを整える必要がありました

Slide 46

Slide 46 text

障壁③ ~useHistory~ TanStack Router が useHistory Hook を提供していない 自前で実装する 障壁 解決策 useHistory とはp € History オブジェクトにアクセスするための Hook € React Router v6 でも廃止

Slide 47

Slide 47 text

障壁③ ~useHistory~ v History オブジェクトを直接利用している箇所が多いため、暫定的に 自前で実4 v 基本的に History オブジェクトを直接操作したくないので、今後は TanStack Router が提供する純正の Hooks に移行する方 v useNavigate, useLocation, useSearch 等

Slide 48

Slide 48 text

障壁③ ~useHistory~ 実装イメージ

Slide 49

Slide 49 text

目次 01 WINTICKET について 02 TanStack Router への移行背景 03 ページの移行 04 Hooks の移行 05 移行してみて 06 おわりに

Slide 50

Slide 50 text

移行してみて 想像以上に各ページのコードの見通しが良い 7 URL から対象ページの実装を たどりやすい 7 ページに付随するロールが一目 でわかる

Slide 51

Slide 51 text

移行してみて 型が通るって最高だ →id に型が通る →パスに型が通る パラメータ、パスに型が通るので開発時も迷わず、タイポも検知できる

Slide 52

Slide 52 text

移行してみて 最近はクエリパラメータのバリデーションも行っています →クエリパラメータに型 →スキーマ定義 p Zod, Valibot 等のバリデーション ライブラリを利用可g p 参考 p yup に基づいたスキーマを提供

Slide 53

Slide 53 text

今後の展望 unstated の脱È ‘ 現在チーム一丸で進行中 ‚ ‘ -Injector.tsx が存在する unstated が利用されているページを TanStack Query に移行‚ ‘ -Injector.tsx が生えたことで、unstated に依存しているページの特定が楽に useHistory の脱È ‘ useNavigate, useParams, useSearch 等の TanStack Router が提供する Hook に移行

Slide 54

Slide 54 text

目次 01 WINTICKET について 02 TanStack Router への移行背景 03 ページの移行 04 Hooks の移行 05 移行してみて 06 おわりに

Slide 55

Slide 55 text

最後に F 移行を楽に進めるためにも、常日頃からコードに秩序をもたせるべ3 F 大規模移行はいくら見通しを立ててもエッジケースに絶対に直面するので 地道に手を動かす必要があ7 F 大規模移行には腕力・覚悟・気合が必要

Slide 56

Slide 56 text

We are HIRING !!! … WINTICKET Web で働いてくれる人を募集していますS … 圧倒的若手チーム(全員20代8 … Web っぽいことだけに閉じずに CI/CD やインフラ周りを触る機会もアリ