×
Copy
Open
Link
Embed
Share
Beginning
This slide
Copy link URL
Copy link URL
Copy iframe embed code
Copy iframe embed code
Copy javascript embed code
Copy javascript embed code
Share
Tweet
Share
Tweet
Slide 1
Slide 1 text
Real World and Type Puzzle Code Generation @yukukotani 2024/05/11 - TSKaigi 2024
Slide 2
Slide 2 text
小谷 優空 - @yukukotani ・Software Engineer @ Ubie, Inc. (2019/05~) ・Lead Architect ・Maintainer of Kuma UI ・Student @ Univ. Tsukuba (2019/04~) ・情報科学類 自己紹介
Slide 3
Slide 3 text
趣旨 型安全なコードを吐くコードジェネレータを作る野暮用があった(あるある) → 型安全 + コードジェネレータ = → 観察してエッセンスを抽出しよう! Prisma
Slide 4
Slide 4 text
観察するスキーマ このスキーマから生成されるコードを観察する User と Post がリレーションを持つ簡単なやつ
Slide 5
Slide 5 text
型パズルでselectした値に型をつける
Slide 6
Slide 6 text
No content
Slide 7
Slide 7 text
No content
Slide 8
Slide 8 text
簡略化した実装を観察する これが動くPrismaClient型(findFirst関数)の実装 スキーマに即した型がつく TS Playground
Slide 9
Slide 9 text
PrismaClient型 実際はクラスとして実装されているが、ここではシンプルなオブジェクト型に
Slide 10
Slide 10 text
入力の型を得る Type Argument Inferenceによって 型引数Tが推論される
Slide 11
Slide 11 text
入力の型を得る 型引数の制約は入力時の補完・エラー用。 GetSelectIncludeResultの導出には 条件型を使うので無関係
Slide 12
Slide 12 text
入力の型を得る 型引数の制約は入力時の補完・エラー用。 GetSelectIncludeResultの導出には 条件型を使うので無関係
Slide 13
Slide 13 text
出力の型を導出する 続いて出力の型を組成する要素を見ていく
Slide 14
Slide 14 text
$XxxPayload型 スキーマのモデルと対応して生成される カラムの型や他モデルとのリレーションを表現 → select結果の型を決定するための マスタとして機能 schema.prisma 生成される型
Slide 15
Slide 15 text
出力の型を導出する 第2型引数には入力の型をそのまま渡す
Slide 16
Slide 16 text
出力の型を導出する いよいよ出力の型そのものを見ていく
Slide 17
Slide 17 text
GetSelectIncludeResult 事前生成のPayload型(P)とユーザーが渡す引数(A)から、select結果の型を導出
Slide 18
Slide 18 text
GetSelectIncludeResult 条件型 `T extends { foo: infer S } ? S : never`パターンで、selectした中身を取る
Slide 19
Slide 19 text
GetSelectIncludeResult Mapped Types で型を組み立てる
Slide 20
Slide 20 text
GetSelectIncludeResult selectしたキーを Mapped Types のキーとする
Slide 21
Slide 21 text
GetSelectIncludeResult キーに対応する値がfalse等の場合はneverにキャストして除外 Mapped Typesのキーを neverにすると丸ごと消える
Slide 22
Slide 22 text
GetSelectIncludeResult Mapped Typesの値側を見ていく
Slide 23
Slide 23 text
GetSelectIncludeResult Payload型を参照して、selectする値の型を取る
Slide 24
Slide 24 text
おさらい:$XxxPayload型 select結果の型を決定するためのマスタ schema.prisma 生成される型
Slide 25
Slide 25 text
GetSelectIncludeResult scalarsではなくobjectsの場合も同様に取り、再帰的にselect結果を導出
Slide 26
Slide 26 text
GetSelectIncludeResult 条件型に当てはまらなければneverに落とすが、最初の型制約で先に落ちるはず
Slide 27
Slide 27 text
まとめ:型パズルのエッセンス 型パズルを要素分解すると組み立てやすq Uf 型引数の推論によって入力の型を得 Df 型引数の制約を につけ E インターナルな型引数は補完不要なので頑張らなくて良q Bf 入力の型を条件型で絞り込みながら出力の型を組み立て E 事前生成したマスタ型(Payload)を参照してシンプル化 入力時の補完・エラーのため
Slide 28
Slide 28 text
型パズルをできるだけ頑張らない
Slide 29
Slide 29 text
なるべく事前生成して型計算を避ける Payload型から型パズルで導出できそうだが 重複を許容してすべて静的にコード生成する
Slide 30
Slide 30 text
なるべく事前生成して型計算を避ける モデルの一般化もせず重複生成
Slide 31
Slide 31 text
なるべく事前生成して型計算を避ける 事前生成ファイルは基本編集しないので、パースコストの影響が限定的 → ファイルサイズが大きくなってもOK 型チェックはfindFirstとかを触るたびに発生する(tsc内部でキャッシュは効くが) → パフォーマンスがDXに直撃する さらにビルド時間でもパースに比べて型チェックのほうが影響が大きい prisma/prisma の tsc trace パース時間 型チェック時間
Slide 32
Slide 32 text
なるべく事前生成して型計算を避ける リアルタイムな入力に対してフィードバックしたい部分に絞って型計算をする その他はできるだけ静的にコード生成する
Slide 33
Slide 33 text
コード生成もできるだけ頑張らない
Slide 34
Slide 34 text
No content
Slide 35
Slide 35 text
No content
Slide 36
Slide 36 text
コード生成するコードは基本的につらい テンプレートリテラルなりASTビルダなりで組み立てるので見通しは最悪 なるべく生成するパターンを減らしたい
Slide 37
Slide 37 text
ライブラリとして切り出す 生成するコードのうち静的な部分はライブラリに切り出して、 生成コードからは参照するだけ 生成したコード
Slide 38
Slide 38 text
ライブラリとして切り出す Tips: ライブラリ内で使っているinternalな型もすべてexportする exportしていないと、この型を参照する他パッケージの.d.tsに インライン化されて爆発する Ref: https://github.com/prisma/prisma/blob/main /packages/client/src/runtime/core/types/exported/README.md
Slide 39
Slide 39 text
型以外は生成しない 生成された .d.ts 3500行に対して .js は200行程度 コード生成で頑張るのではなく Proxy を使った動的操作をしている
Slide 40
Slide 40 text
まとめ H リアルタイムな入力にフィードバックしたい部分だけは型パズルを頑張5 H 型パズルを要素分解して順番に考えるとやりやすF H それ以外はコードの重複を許容して事前生成に振り切5 H 完全に静的な型は生成すらせずライブラリÆ H 型以外のランタイム処理はProxyで頑張る
Slide 41
Slide 41 text
Thanks! Ubieのブースにいます!