Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
デッドコード撲滅のためにエンドポイントの棚卸し機能を作った話 〜ESLintカスタムルールとt...
Search
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
Taiga KATARAO
June 04, 2024
Technology
340
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
デッドコード撲滅のためにエンドポイントの棚卸し機能を作った話 〜ESLintカスタムルールとtypescript-estree利用のすすめ〜
Taiga KATARAO
June 04, 2024
More Decks by Taiga KATARAO
See All by Taiga KATARAO
CloudbaseのTypeScript事情~【TypeScript特集】1時間で6社と出会う!採用ピッチイベント~
tarao1006
0
16
Other Decks in Technology
See All in Technology
2026TECHFRESH畢業分享會 - Lightning Talk - E起 See See : 電商推薦讀心術? 數據說了算
line_developers_tw
PRO
0
790
ポケモンの型をTypeScriptの型システムで表現してみた
subroh0508
0
370
LLMにもCAP定理があるという話
harukasakihara
0
290
How Timee Delivers Day 1 Production Ready LLM Features
tomoyks
0
130
Microsoft Build Keynoteふりかえり
tomokusaba
0
120
失敗を経て、Harness Engineering で 大切にしたいことを考える / Learning from Failure: What Matters in Harness Engineering
bitkey
PRO
1
310
DevOps Agentで始めるAWS運用 〜フロンティアエージェントが変える運用の現場〜
nyankotaro
1
380
ルールやカスタム機能、どう活かす?ハンズオンで体感するIBM Bobの出力コントロール
muehara
1
130
NAB Show 2026 動画技術関連レポート / NAB Show 2026 Report
cyberagentdevelopers
PRO
0
170
攻撃者視点で考えるDetection Engineering
cryptopeg
1
1.2k
protovalidate-es を導入してみた
bengo4com
0
170
2026.06.13_AI時代に事業会社が「SIer出身エンジニア」を求める理由 / Why Businesses Seek Engineers with a System Integrator Background in the AI Era
jumtech
0
1k
Featured
See All Featured
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
Bash Introduction
62gerente
615
220k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
55k
Navigating Team Friction
lara
192
16k
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
370
Statistics for Hackers
jakevdp
799
230k
Typedesign – Prime Four
hannesfritz
42
3.1k
Become a Pro
speakerdeck
PRO
31
6k
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
720
The Cost Of JavaScript in 2023
addyosmani
55
10k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
231
23k
Fireside Chat
paigeccino
42
3.9k
Transcript
デッドコード撲滅のためにエンドポ イントの棚卸し機能を作った話 〜ESLintカスタムルールとtypescript-estree利用の すすめ〜 Cloudbase株式会社 tarao (Taiga Katarao)
© 2024 Cloudbase Inc. tarao (Taiga Katarao) Cloudbase株式会社 ソフトウェアエンジニy x
バックエンド〜Webフロントエンドまで TypeScriptで開6 x より興味があるのはWebフロントエンド @tarao1006 @tarao1006
© 2024 Cloudbase Inc. 話すこ と F ESLintのカスタムルールの一R F 実装方法を逐一解説することはしなB
F 全貌はZennに投稿済y F エッセンス (& 時間が余れば記事投稿後の取り組みの紹介) https://zenn.dev/cloudbase/articles/list-endpoints
Cloudbaseの技術スタック © 2024 Cloudbase Inc. RDB API Server GraphDB Data
Loader Storage スキャナー お客様のクラウド環境 Web Frontend お客様
© 2024 Cloudbase Inc. 素朴にExpressとSWRを使用し ていた T Expressで素朴にルーティング T SWRで素朴にデータフェッチ
T useFetchはuseSWRの薄いwrappe T 今日はClient Componentの話 T エッセンスはServer Componentにも展開可能なはず const = => ... ( , ) { }; app. ( , handler); handler get req res " " /v1/foo/:fooId const = => const = ... () { { } < >( fooId ); }; Component useFetch ResponseType data `/v1/foo/${ }`
© 2024 Cloudbase Inc. 呼び出されていないエンドポイン トあり ませんか ? w 「v2を追加した時にv1を消し忘れた」とか「そのエンドポイン
トを使用しているページがなくなったr w 新任者の無駄なキャッチアップコストになるなど、未使用エンド ポイントはないに越したことはな w Expressのルーティングは変数ではないため、未使用変数として 検出するといったことはできな w 「消し忘れないように注意する」は解決策にならない
© 2024 Cloudbase Inc. 解決を試みる I 定義済みエンドポイント集合から使用エンドポイント集合を引け ば、未使用エンドポイント集合が分かるはず GitHub Actionsで怒られるようになった
使用エンドポイント 未使用エンドポイント
© 2024 Cloudbase Inc. 定義済みエンドポイン トの列挙 8 割愛 t 8
Expressのルーターをゴニョゴニョすることで実現可d 8 Zenn: 状態5: バックエンドでエンドポイントの棚卸しを自動化
© 2024 Cloudbase Inc. 使用エンドポイン トの列挙 v 難儀 q v
APIサーバーと違ってルーターのようなものは存在しなG v ESLint & @typescript-eslint/typescript-estreeが大活躍した https://github.com/typescript-eslint/typescript-eslint
Step1: urlcat導入 フロントエンドでもExpressと同じ文字列を使ってAPIリクエストする ようにするためにurlcatを導入 © 2024 Cloudbase Inc. const =
=> const = ... () { { } ( ( , { fooId })); }; Component useFetch urlcat data " " /v1/foo/:fooId const = ( v1 foo :fooId , { fooId: foo , barId: bar }); console. (path) path urlcat log "/ / / " " " " " // /v1/foo/foo?barId=bar https://github.com/balazsbotond/urlcat
Step1: urlcat導入 、、、全然定着しない © 2024 Cloudbase Inc.
Step2: 型で縛る U エンジニアが増えることも考えると毎回PRで指摘するのはサステナ ブルじゃなa U TypeScriptなので型で縛ろう © 2024 Cloudbase
Inc.
Step2: 型で縛る(定義側) // /foo/:fooId/bar/:barId のような文字列から // { fooId: string }
& { barId: string } のような型を生成する型 type extends = class extends constructor private : private : private : return ... ... const = < > ...; < > { ( , < >, < , >) {} () { ( .pathname, { .params, .searchParams }); } } < >(path: Path< >) => { return (path. ()); } PathParams T Path T T PathParams T Record toString urlcat useFetch useSWR toString string string string any this this this T pathname params searchParams any © 2024 Cloudbase Inc. useFetchの引数をstringからPathに変更し、 Pathクラスを通してurlcatの使用を強制
Step2: 型で縛る(使用側) © 2024 Cloudbase Inc. const = => const
= new () { { } < >( ( , { fooId }) ); } Component useFetch ResponseType Path data "/v1/foo/:fooId" Pathを渡すことを強制
Step2: 型で縛る(使用側) © 2024 Cloudbase Inc. const = => const
= new () { { } < >( ( , { fooId }) ); } Component useFetch ResponseType Path data "/v1/foo/:fooId" const = => const = new () { { } ( (` ) ); } Component useFetch ResponseType Path data < > /v1/foo/${fooId}` Pathを渡すことを強制 Oh... テンプレートリテラルを渡すという抜け道
Step3: ESLintで縛る j 型で縛りきれないのならESLintで縛ろう (詰んだと思っていたが耐えたr j 調べてみたらESLintの世界では文字列リテラルとテンプレートリテ ラルを区別できるぽい!P j
というわけで、PathクラスのNewExpressionの第一引数が TemplateLiteralだった場合にエラーになるルールを作れば良い(実 際には文字列リテラル以外を禁止) © 2024 Cloudbase Inc.
Step3: ESLintで縛る export const = => return for const of
if === && === && !== ESLintUtils.RuleCreator. < , >({ : ( , [ ]) { { ( ) { ( options) { ( node.callee.type node.callee.name option.className node.arguments[option.argumentIndex].type ) { context. ({ node, messageId: , data: { className: option.className, argumentIndex: (option.argumentIndex), }, }); } } }, }; }, }); rule option withoutDocs Options MessageId create NewExpression report ordinal context options node "Identifier" "Literal" "restrict-literal-argument" © 2024 Cloudbase Inc. 実装の雰囲気
Step3: ESLintで縛る useFetch Path useFetch useFetch useFetch useFetch Path useFetch
Path useFetch Path ( ( , { fooId })); ( fooId ); (文字列変数); (文字列を返す関数呼び出し); ( ( fooId )); ( (文字列変数)); ( (文字列を返す関数呼び出し)); new new new new "/v1/foo/:fooId" `/v1/foo/${ }` `/v1/foo/${ }` © 2024 Cloudbase Inc. ESLintでエラー 型でエラー
Step4: typescript-estreeを使って使用エンドポイントを列挙 Pathクラスの第一引数は文字列リテラルしかあり得なくなったので、あとは Pathクラスの第一引数を列挙すれば、使用エンドポイントを列挙できそ う!!c でも、どうやって列挙する、、、? m
気合いで正規表現P というか、ESLintのカスタムルール作った時にエラー吐いてるところをstringの arrayにpushする実装に変えられたらめっちゃ簡単に列挙できるのでは? © 2024 Cloudbase Inc.
Step4: typescript-estreeを使って使用エンドポイントを列挙 g @typescript-eslint/typescript-estree !!!!p g “A parser that produces
an ESTree-compatible AST for TypeScript code.” であり@typescript- eslint/parserの内部で使用されているr g ESLintのカスタムルールとほぼ同じように書けて学習コストが低いのでおすすめ © 2024 Cloudbase Inc. const = new if === && === && === && typeof === < >() (node) { ( node.callee.type node.callee.name node.arguments[ ].type node.arguments[ ].type ) endpoints. (node.arguments[ ].value); } } endopoints string 0 0 0 Set NewExpression add "Identifier" "Path" "Literal" "string" NewExpression report ordinal (node) { ( options) { ( node.callee.type node.callee.name option.className node.arguments[option.argumentIndex].type ) { context. ({ node, messageId: , data: { className: option.className, argumentIndex: (option.argumentIndex), }, }); } } } for const of if === && === && !== option "Identifier" "Literal" "restrict-literal-argument"
Step5: 型パラメータに制約をつける v 実は、Expressから定義済みエンドポイントを列挙する際にユニオン型にしてし まえば、Pathの型引数に制約をつけられu v ただし、この制約をつけたとしても前述したカスタムルールやtypescript- estreeによるASTの走査は依然必P v なぜか
© 2024 Cloudbase Inc. type = | | class extends constructor private : private : private : ; < > { ( , < >, < , >) {} } GatPathname Path T GetPathname T PathParams T Record "/v1/foo/:fooId" "/v2/foo/:fooId" "/v1/bar/:barId" pathname params searchParams string any Expressからユニオン型を生成して型引数に制約をつける
Step5: 型パラメータに制約をつける 4 TypeScriptのユニオン型が必要十分であるかを検証するのは難し& 4 結局、使用している値を列挙する仕組みは必要 © 2024 Cloudbase Inc.
type = | const : = AorB AorB "A" "B" "A" A で十分 type = AorB "A"
© 2024 Cloudbase Inc. 今後の展望と共に取り組みをZennに投稿 Zenn: 今後の展望
© 2024 Cloudbase Inc. 今後の展望と共に取り組みをZennに投稿 Zenn: 今後の展望 やりました やりました
Schema firstに移行 p これまでは「Express → エンドポイント列挙」であったため、 Expressを起動しないとエンドポイントを列挙できず、パフォーマン スの問題があっC p 「エンドポイント列挙
→ Express」のように向きを変えることで問 題を解7 p GraphQLにおけるschema firstのようなイメージ © 2024 Cloudbase Inc.
Schema firstに移行 © 2024 Cloudbase Inc. app. ( , );
app. ( , ); app. ( , ); get get post "/v1/foo/:fooId" "/v2/foo/:fooId" "/v1/foo" ... ... ... app. ( , ); app. ( , ); app. ( , ); get get post "/v1/foo/:fooId" "/v2/foo/:fooId" "/v1/foo" ... ... ... type = type = ; ; GetPathname PostPathname "..." "..." const = { : z. ({ request: z. ({ pathParams: z. ({}), queryParams: z. ({}), body: z. ({}), }), response: z. ({}), }), : z. ({ ... }), : z. ({ ... }), }; schema "GET /v1/foo/:fooId" "GET /v2/foo/:fooId" "POST /v1/foo" object object object object object object object object メソッドとパス リクエスト/レスポンスの情報
Schema firstに移行 ) スキーマにリクエスト/レスポンスの型の情報があるのでgenericsが 不要になった © 2024 Cloudbase Inc. const
= { } < >( fooId ); data useFetch ResponseType `/v1/foo/${ }` const = new { } ( ( , { fooId })); data useFetch Path "/v1/foo/:fooId"
© 2024 Cloudbase Inc. まとめ d コーディングルールはESLintのルールで表現できると良b d ASTにおいてはstringリテラルとテンプレートリテラルを区別で きf
d typescript-estreeはESLintの知識を活かしてASTをいじれるよう になるのでおすすe d (REST APIであってもエンドポイントの定義はSchema firstにし ておくと便利)
We Are Hiring! Engineer Entrance Book