GraphQL
イベント
該当するコンテンツが見つかりませんでした
マガジン
該当するコンテンツが見つかりませんでした
技術ブログ
目次 はじめに イベントの様子 スポンサーブース ブース企画 1. アンケートボード「あなたのAI活用、どこまで来てる?」 2. 食クイズ&くじ引き エントランス企画ゾーン セッション紹介 ビジネスモデルから紐解く、AI+型駆動開発 型の「重心」はビジネスモデルで決まる 型の Origin と AI への渡し方 SaaS 型 — フォーム状態を Discriminated Union で渡す マーケットプレイス型 — 中央スキーマを Origin として渡す 所感 TS 7: How We Got There なぜ Go だったのか Snapshot テストと Differential Fuzzing 所感 制約と時代から読み解く TypeScript コンパイラ設計史 TypeScript Compiler の「独特さ」 設計を決めた時代背景と JS の制約 Go port による改善 所感 いつテストを書くか?―ソフトウェア開発における安心と不安について考える 保守性の本質は「変更容易性」である 変更容易性の 2 層モデルと、開放閉鎖原則の再解釈 テストは「不安」と「構造」にフィードバックを与える いつテストを書くか? 各社スポンサーブース レバレジーズさん プレイドさん ZOZOさん まとめ アフターイベントのご案内 最後に はじめに 2026年5月22日(金)、23日(土)に開催された TSKaigi 2026 に、弊社の開発本部から 6 名のエンジニアが参加してきましたので、イベントの様子や印象に残ったセッションをご紹介します。 各セッションのアーカイブも公開予定とのことですので、ぜひ公式サイト・YouTube チャンネルなどをチェックしてみてください。 2026.tskaigi.org www.youtube.com イベントの様子 スポンサーブース エブリーは今回、ゴールドスポンサーとしてブースを出展させていただきました! 足を運んでいただいた皆様、本当にありがとうございました! ブース企画 1. アンケートボード「あなたのAI活用、どこまで来てる?」 ブースでは、エンジニアの皆さんのAI活用状況を伺うアンケートを実施しました。 回答いただいた多くの皆様、ありがとうございました!最終結果はこちらです……! キャリア0〜5年の若手から15年以上の大ベテランまで、非常に幅広い層のエンジニアの皆様に回答いただきました! 結果としては、「指示は出すが都度レビュー」「並列で動かしている」という回答が多く、「AIに全任せするのはまだ難しい...」「並列で回すとコンテキストスイッチが大変...!」といった声も寄せられ、興味深い結果となりました! 2. 食クイズ&くじ引き さらに、エブリーとしては初の試みとなる「クイズ企画」も行いました! クイズに参加していただくことで、キッチングッズ(まな板、計量スプーン、しゃもじ、お箸)が当たるくじを引けるという形式です。 クイズは大盛況で、「これ見たことある!答えなんだっけ!」「簡単じゃん〜……ってうわ!間違えた!」と、皆さん真剣かつ楽しそうに頭を悩ませてくださる姿が印象的でした。ブース担当メンバーも、皆さんのリアクションを見ながら一緒に盛り上がることができ、企画して本当に良かったと感じています! エントランス企画ゾーン Day1にネイル企画がありプロのネイリストによるネイル体験をしてきました! メニューは「ネイルアート」か「ネイルケア」のいずれか好きな方を選ぶことができました。 綺麗にケアされた自分の手元が視界に入るたびにテンションが上がり、大満足の体験でした!キーボードを叩くモチベーションも爆上がりです!! セッション紹介 ビジネスモデルから紐解く、AI+型駆動開発 2026.tskaigi.org 発表者: omote (株式会社 estie) レポート: 江﨑 estie の omote さんによるセッション「ビジネスモデルから紐解く、AI+型駆動開発」について紹介します。スライドは以下で公開されています。 speakerdeck.com 本セッションは「AI 時代における開発のスタート地点はどこか」という問いから始まり、開発体験やフロントエンドのベストプラクティスではなく ビジネスモデルこそが設計の起点になる という仮説のもと、ビジネスモデル別に「型設計を AI とどう組むか」を整理していく構成でした。 型の「重心」はビジネスモデルで決まる 型はクライアント・API・データのどこにでも書けますが、設計エネルギーを最も注ぐ場所はプロダクトごとに違います。omote さんはその場所を 型の重心 と呼び、技術スタックではなく事業構造で分類すべきだと整理していました。具体的には、プロダクトを次の 4 つに分けています。 分類 価値の源泉 型の重心 型の Origin インタラクション中心 SaaS 型 ユーザー入力・編集体験 UI 状態・フォーム・権限 TypeScript 自身 データ依存型 外部データ・ドメイン知識 API レスポンス・ドメインモデル DB / GraphQL スキーマ API 中心マイクロサービス型 API 契約そのもの コントラクト・スキーマ定義 .proto / OpenAPI マーケットプレイス型 マッチング・取引の成立 取引データのスキーマ 中央ドメインモデル 価値の源泉と重心、Origin がフラットに 4 分類で並ぶことで、自分の関わっているプロダクトをそのまま当てはめながら聞ける構成になっていました。 型の Origin と AI への渡し方 もう一段下の問いとして「型はどこから来るのか(Origin)」も提示されていました。SaaS 型・マーケットプレイス型では人間が Origin を定義する側になり、データ依存型・API 中心型では既存スキーマや契約から型を受け取る側になります。つまり TypeScript の役割はビジネスモデルによって 定義する側 と 受け取る側 の 2 つに分かれる、という整理です。 ここを踏まえると、AI に渡す「文脈の深さ」によって出力の質も変わってきます。 Level 1: 自然言語だけ Level 2: TypeScript の派生型を渡す Level 3: Origin 自体、または Origin に近い型情報を渡す Level 3 まで踏み込むと、データソースの制約や整合性まで型として運ばれるため、Claude の出力が事業文脈と整合した実装になりやすい、という話でした。実例として、SaaS 型とマーケットプレイス型の 2 つが、Claude への入力と出力の対比で示されていました。 SaaS 型 — フォーム状態を Discriminated Union で渡す 人間側では、フォームの状態と送信エラーを Discriminated Union で定義した型を Claude に渡します。 type InviteMemberFormState = | { status: "editing"; email: string; role: Role; errors: ValidationError[] } | { status: "confirming"; email: string; role: Role } | { status: "submitting"; email: string; role: Role } | { status: "succeeded"; invitedEmail: string } | { status: "failed"; email: string; role: Role; error: SubmitError }; type SubmitError = | { code: "already_invited"; existingMemberEmail: string } | { code: "quota_exceeded"; currentCount: number; limit: number } | { code: "network_error" }; type Role = "admin" | "editor" | "viewer"; この型を添えて「この InviteMemberFormState を満たす React コンポーネントを実装してください」と依頼すると、Claude は失敗時の表示を次のような switch で出力します。 function FailedView ( { error , onRetry , onCancel } : FailedViewProps ) { switch (error.code) { case "already_invited" : return < p > { error.existingMemberEmail } はすでにメンバーです </ p > ; case "quota_exceeded" : return ( < p > 招待上限( { error.limit } )に達しています(現在 { error.currentCount }{ " " } 名) </ p > ); case "network_error" : return < p > ネットワークエラーが発生しました。再度お試しください </ p > ; } // ↑ 'code' の網羅性が型レベルで保証される。新エラー追加時もここでコンパイルエラー } 各エラーコード固有のプロパティ( existingMemberEmail ・ limit ・ currentCount )に型安全にアクセスできており、 switch の網羅性チェックが型レベルで効いている点が示されていました。新しいエラーコードを追加した際にも同じ箇所でコンパイルエラーになるため、AI 出力に対するガードレールが型で担保されているのがよく分かります。 マーケットプレイス型 — 中央スキーマを Origin として渡す マーケットプレイス型では、売り手・買い手・運営・取引整合性といったあらゆる方向の関心が、中央スキーマ Transaction に集約されます。これを Origin として Claude に渡す形になります。 // あらゆる方向からの依存が、ここに集約される type Transaction = { id: TransactionId; sellerId: UserId; // 売り手の関心 buyerId: UserId; // 買い手の関心 price: number; platformFee: number; // 売り手・運営の関心 shippingAddress: Address; // 買い手のみ trackingNumber: string | null; status: TransactionStatus; // Discriminated Union(全方向の関心) // ... タイムスタンプ群、双方向の評価 }; 「この中央スキーマから SellerTransactionView と BuyerTransactionView を派生させてください」と依頼すると、Claude は Pick で必要なフィールドだけを切り出し、 netRevenue や totalPaid といった派生プロパティを足した型を出力します。 // 売り手向け type SellerTransactionView = Pick< Transaction, | "id" | "itemId" | "buyerId" | "price" | "platformFee" | "shippingFee" | "trackingNumber" | "status" | "buyerRating" > & { netRevenue: number; }; // 除外: shippingAddress, sellerRating // 買い手向け type BuyerTransactionView = Pick< Transaction, | "id" | "itemId" | "sellerId" | "price" | "shippingFee" | "shippingAddress" | "trackingNumber" | "status" | "sellerRating" > & { totalPaid: number; }; // 除外: platformFee, buyerRating Transaction 自体には手を加えず、 Pick で必要なフィールドだけを切り出して派生型を機械的に生成している点がポイントで、「何を売り手に見せ、何を買い手に見せるか」という事業判断は中央スキーマの段階で人間が決めておかなければならない、という対比になっていました。 所感 プロダクト開発の設計と言われると、つい開発体験やフロントエンドのベストプラクティス側から話が始まりがちです。それに対し、本セッションでは 起点をビジネスモデルに置き、型の重心と Origin で具体まで落とす という流れが一貫していて、ビジネスモデルと型の関係性が非常に丁寧に整理されていたのが印象的でした。 実装が AI に委ねられるからこそ、人間が残すべき仕事は「型をどこに置くか」「Origin をどう定義するか」という意思決定だ、というメッセージも明確で、AI と分担して開発を進める際に人間側に残すべき判断をはっきりさせてくれる内容でした。自分が関わっているプロダクトについても、まずは「型の重心はどこにあるのか」を棚卸しするところから取り入れてみたいと感じました。 TS 7: How We Got There 2026.tskaigi.org 発表者: Jake Bailey (Microsoft) レポート: パンダム/rymiyamoto 私からは Microsoft の Jake Bailey さんによるキーノート「TS 7: How We Got There」について紹介します。スライドは以下で公開されています。 jakebailey.dev 本セッションでは、TypeScript の処理系を JavaScript から Go へ移植する取り組みについて、その動機・ポートを支えた手法・段階的なリリース戦略が紹介されました。 なぜ Go だったのか tsc / tsserver は数千万行規模のコードベースや 2,000 個以上の tsconfig.json を扱うユーザーが現れる中で、JavaScript ランタイムの制約(スレッド間でのオブジェクト共有不可、async/await の関数色分け、4GB のメモリ上限)からパフォーマンス改善が限界に近づいていたとのことです。 「rewrite ではなく port」を前提に Rust / C# / Zig などと比較したうえで Go が選ばれた理由として、以下が挙げられていました。 TypeScript(class より data + 関数 + 構造的 interface 寄り)と構造が近く、コードを 1:1 で移し替えやすい GC があるため AST の循環参照を意識せずに書ける goroutine による並行性を、関数の色分けなしに導入できる 学習コストが低く、チームの 10 名ほどが数日で生産的になれた ライブラリ単体ではなく「チーム全員が短期間で動ける」観点で言語選定している点は印象的でした。 Snapshot テストと Differential Fuzzing ポートを支えた手法として、特に Snapshot(ベースライン)テストと Differential Fuzzing が紹介されていました。 Snapshot テストは、出力をファイルとしてリポジトリにコミットし、PR の diff で挙動変化を確認する手法です。TypeScript では新旧コンパイラの出力差分そのものを baseline として扱い、その差分ファイルが消えること自体をポート完了の指標にしているとのことでした。 Differential Fuzzing は、新旧の実装に同じ入力を fuzzer から流し込み、結果が一致しない入力を自動で発見させる手法です。Go では組み込みの fuzzer で以下のように記述できます。 func FuzzToFileNameLowerCase(f *testing.F) { f.Add( "foo/bar/baz.ts" ) f.Add( "C:/foo/bar/baz.ts" ) f.Fuzz( func (t *testing.T, p string ) { want := oldToFileNameLowerCase(p) got := ToFileNameLowerCase(p) assert.Equal(t, want, got) }) } ユニットテストで取りこぼしがちなエッジケースを fuzzer に探させる発想は、性能改善で挙動を保ったまま実装を差し替える場面で特に有効で、自身の業務にも持ち帰りたい手法でした。 所感 会場のライブデモでは、 tsc が数分単位で進まない遅さを見せたあと、 tsgo に切り替わった瞬間に 10 秒足らずで型チェックが終わり、スライドの「VS Code(約 2.3M LoC)で tsc 比 約 10 倍・クラッシュ率約 1/20」という数字をその場で体感することができました。また発表中には「昨年の TSKaigi 2025 のタイミングでは tsgo のクラッシュが多かった」という話に触れられて会場が湧いていましたが、その状態から 1 年でテレメトリと継続的な改善でクラッシュをほぼ潰し、ここまで仕上げている開発スピードには、純粋に驚かされました! 特に Differential Fuzzing は、自分が普段触っている Go バックエンドで「速度改善のために旧実装と挙動を変えずに置き換えたい」場面と相性が良さそうで、まずは小さな関数で testing.F を使った新旧比較から試したいと考えています。一方で、ベースライン駆動のレビューは出力ファイルを丸ごとコミットする運用になるため、レビュー差分の見やすさと引き換えにリポジトリ容量や CI の負荷が膨らみそうで、テスト数を踏まえてどこから採用するかは見極めが必要そうです。フロントエンドの TypeScript 側でも、 tsgo が 7.0 として安定すれば CI の型チェック時間が大きく改善する余地があるので、ベータの様子は引き続き追っていこうと思います。 制約と時代から読み解く TypeScript コンパイラ設計史 2026.tskaigi.org 発表者: Yoshiaki Togami (株式会社メルペイ) レポート: 庄司 私からは Yoshiaki Togami さんによるセッション「制約と時代から読み解く TypeScript コンパイラ設計史」について紹介します。 スライドは以下で公開されています。 www.docswell.com 本セッションでは、TypeScript Compiler がなぜ「独特な内部設計」を持つに至ったのかを、 当時の時代背景と JavaScript の制約を交えつつ、Go port による改善までを整理する構成となっていました。 TypeScript Compiler の「独特さ」 冒頭で TypeScript Compiler のパイプライン(Scanner → Parser → Binder → Checker)と、 識別子から宣言へたどる Symbol が紹介され、Compiler の構造的な「独特さ」として 2 つの特徴が挙げられました。 Binder が AST に semantic を後付けするため、意味情報が構文木と同居する AST の親子や Symbol 間でアクセスできるようにするために循環参照が発生する C# コンパイラで採用されている Red-Green Tree というデータ構造を採用することで、immutability と semantic へのアクセス可能性を両立できないかというアイディアもありましたが、 当時の JS では Worker API などもなく immutability を活用しきれない点やメモリ消費が大きくなってしまうという点から見送られました。 設計を決めた時代背景と JS の制約 TypeScript の設計が決まった 2010 年前後には以下のような技術的背景がありました。 2004〜2006 年の Ajax 革命(Gmail / Google Maps / Google Docs)で「JS で本格的なアプリ」が現実味を帯びる 2008 年に V8 が公開される。(しかし、ES5 段階では Worker API がなく最適化も未熟) JS ツーリングが貧弱で開発体験が悪い これらを踏まえて、以下のような3つのアプローチが取られました。 JS を遠ざける(Script# 等、別言語で書いて JS にトランスパイルする) JS 自体を置き換える(Dart 等) JS のスーパーセットとして型を追加する(Strada -> TypeScript) これは「ターゲット言語からそこまで離れるくらいなら JS 自体を直すべきでは」という発想から出たアイディア 結果的には、SharedArrayBuffer 等の共有メモリ機構が未整備で、オブジェクトヘッダのメモリオーバーヘッドも大きいという背景から、 immutability やメモリ消費を犠牲にして、現在の「AST が意味情報を背負う・循環参照あり・単一ツリー」という設計になった、という整理でした。 Go port による改善 最後には、ここまで触れてきた制約が Go port でどう解消されるのかが紹介されました。 tsc → tsgo の高速化の背景として、大きく 2 つのポイントが挙げられました。 単一の Go バイナリへのネイティブ化によるウォームアップリードタイムの削減 共有メモリによるキャッシュヒット率改善やマルチスレッド並列化 所感 TypeScript Compiler の内部設計の独特さについて、当時の時代背景込みで聞ける機会は少なく、とても面白かったです。 当時の JS 特有の仕様的制約から現実解を模索していった流れは技術選定全般への向き合い方としても興味深いものでした。 利用している技術がどんな課題を解決するために生まれたソリューションなのかを知ることが、 ツールを使いこなす上でも重要であることを改めて感じることができました。 いつテストを書くか?―ソフトウェア開発における安心と不安について考える 2026.tskaigi.org 発表者: lacolaco (株式会社TwoGate) レポート: 黒髙 私からは TwoGate の lacolaco さんによるセッション「いつテストを書くか?―ソフトウェア開発における安心と不安について考える」について紹介します。スライドは以下で公開されています。 bit.ly 私自身、lacolaco さんがパーソナリティを務めるポッドキャスト「 リファラジ|リファクタリングとともに生きるラジオ 」のリスナーだったこともあり、本セッションは聞く前から楽しみにしていました。 加えて最近は AI にテストを書かせる場面が増えたことで「そもそもテストを書く意義はなんだったか」が曖昧になってきており、自分がどう向き合うべきかを改めてはっきりさせたい、というのが個人的な動機でした。 本セッションは、Agentic Coding が広がる中であらためて「テストを書く意義」を問い直すために、 ソフトウェアの本質的な性質である「変更容易性」 を補助線として、テスト駆動開発と開放閉鎖原則の関係を整理しなおしていく構成でした。 保守性の本質は「変更容易性」である このセッションは、AI によって開発速度が上がったからこそ「保守性の限界」がすぐ目に見える形で訪れるようになっている、という提起から始まります。保守性の本質は 変更容易性 であるという前提のもと、「変更容易性はソフトウェアに内在する性質ではなく、人間とソフトウェアとの関係として現れる」と説明されていました。 変更容易性の 2 層モデルと、開放閉鎖原則の再解釈 変更容易性は次の2つの独自定義で捉え直されていました。 予期的変更容易性 : 変更を行う「前」に開発者が抱く「変更のしやすさ」の感覚。必要な作業量・影響範囲・成功確率・失敗時のリスクへの予期。要するに、 開発者の不安や恐怖の程度 そのものである。 経験的変更容易性 : 変更を行っている「最中」に開発者が経験する「変更のしやすさ」の感覚。実際にかかった労力や、影響した範囲、引き起こした副作用。 ソフトウェア側から返ってくる「変更への抵抗」の程度 である。 そして、ソフトウェアが「ソフト」である状態は この 2 つの変更容易性が両立している状態 として定義しなおされます。「容易そうだと思える」ことで変更の機会そのものが増え、「実際に容易である」ことで変更の合理性が高まる。アーキテクチャはこの両立を実現する構造を目指すものだ、という整理です。 同時に、ご存知の方も多いでしょう、 開放閉鎖原則 (OCP) がアーキテクチャの根本原理として登場します。 修正に対して閉じている : 既存の要求を満たす振る舞いが変わらないこと(=安定性) 拡張に対して開いている : 既存の要求を満たしたまま新しい要求に応えられること(=柔軟性) この 2 つが両立することで、変更への不安は解消し、労力も軽減される。つまり OCP の達成は予期的変更容易性と経験的変更容易性の両方を高めることに直結する、という形で、 OCP を「変更容易性そのもの」と等価に位置づけ直す再解釈 が行われました。 「変更容易性」を変更の 前 と 最中 という時間軸で分けながら、その 2 層の両立を 開放閉鎖原則 という一本軸に落とし込んでいくことで、自分の中にあった「テストを書く理由」の曖昧さがそのまま整理されていくような感覚がありました。 テストは「不安」と「構造」にフィードバックを与える さらに、「変更容易性とはコードベースではなく、それを変更しようとしている人間が抱く感覚に依存する」という視点のもと、テスト駆動開発が変更容易性に対して 2 つの方向からフィードバックを与える と整理されていました。 ひとつ目は 予期的変更容易性へのフィードバック 、つまり「開発者の不安を取り除く」役割です。テストケースは「既存の要求と期待される振る舞いの定義」であり、変更しても既存テストが壊れないなら、その範囲では「閉じている」ことが保証されているということです。 ふたつ目は 経験的変更容易性へのフィードバック 、すなわち「構造上の問題を教える」役割です。機能追加が既存のテストに影響するならそのモジュールは閉じていないし、新しい機能のテストを書くのが大変ならそのモジュールは開いていないということになります。 そして開放閉鎖原則そのものは、設計のループの中で 徐々に 満たされていくものとして描かれます。 ここで特に印象的だったのは、開放閉鎖原則を 「達成すべきゴールではなく、漸近していく理想状態」 として位置づけていた点です。OCP は完全には満たせないし、変更容易性は変更してみないとわからない。だからこそ、テストと TDD は OCP を徐々に満たしていくためのワークフローとして必要になる、という説明の流れにはとても納得感がありました。 いつテストを書くか? ここまで積み上げてきた整理を踏まえて、本セッションのタイトルに立ち戻ります。テストを書くべきタイミングは、 変更容易性のどちらが阻害されているか で見え方が変わる、というのが lacolaco さんの答えでした。 予期的変更容易性が低いとき (=変更するのが怖いとき)は、 不安を取り除くためにテストを書く 。実装を支えるテスト。逆に、不安がないなら書く必要はないし、書いても不安が変わらないなら書く意味もない。 経験的変更容易性が低いとき (=構造に問題を感じているとき)は、 構造を学習するためにテストを書く 。設計(リファクタリング)を支えるテスト。OCP が十分に満たされ構造を熟知しているなら書く必要はない。 結合度の高いテスト(結合テスト)は不安の解消に、結合度の低いテスト(ユニットテスト)は構造のフィードバックに、という区別がテストピラミッドの図解で整理されておりとても分かりやすかったです。 このセッションが個人的にとても良かったのは、「いつテストを書くか?」という極めて実践的な問いに対する答えが、 自分がコードの変更に対してどう向き合っているのか(不安なのか、構造に違和感があるのか)への感度を高めること に着地していた点です。テストを書くべきかどうかを「自分の感覚を起点に考える、という視点は、AI と分担して開発する時代だからこそ、改めて言語化して持っておきたい感覚だと感じました。 総じて、ソフトウェアとは何か、保守性とは何かという基礎的な問いから始め、それを「開放閉鎖原則」という古典的な原理で言語化しなおすことで、「自分がどのタイミングでテストに向き合うか」という実践的なところまでイメージができる、学びの深いセッションでした。保守性の限界がこれだけ早く訪れるようになった今こそ、 『テスト駆動開発』 や 『Clean Architecture』 といった書籍をもう一度しっかり読み直して咀嚼する価値があると感じています。 各社スポンサーブース 他社さんのスポンサーブースにもたくさん訪問させていただきました! 各社趣向を凝らしたブースや様々な企画が展開され、会場全体がとても賑わっていました。 いくつかご紹介させていただきます。 レバレジーズさん レバレジーズさんのブースでは、アジャイル開発の業務効率化支援 SaaS「agile effect」について解説していただきました! タスク管理ツールと連携することで進捗把握や調整がしやすくなり、開発プロセス自体を可視化することで感覚に頼らない改善が進められるとのことでした。AI によって開発効率が上がる中で、ボトルネックがどこにあるかを把握する重要性は増していると感じる場面が多く、興味深いサービスでした! プレイドさん プレイドさんのブースでは、弊社でも利用させていただいている CX プラットフォーム「KARTE」について、実際のユースケースを交えて解説していただきました! 設定方法まで丁寧に教えていただき、とてもありがたかったです。ブース内ではおみくじを引かせていただく企画もあり、結果はハズレでしたが、他のブースにはない体験で印象に残りました! ZOZOさん ZOZOさんのブースでは、テックリードお手製の TypeScript クイズに挑戦しました!全問正解者は数人とのことで、なかなか歯ごたえのある内容でした。 型推論の挙動や演算子の細かな仕様など、自分の理解が曖昧だった部分がクイズを通して炙り出され、TypeScript の言語仕様を学び直すきっかけになりました! まとめ TSKaigi 2026 は、TypeScript の最新動向や活用事例から、AI とどう組み合わせて開発を進めるかという最近のトピックまで幅広く語られ、TypeScript コミュニティの盛り上がりを改めて感じられる、とても素敵なイベントでした。 特に今年は、 tsgo / TypeScript 7 のキーノートや TypeScript Compiler の設計史といった 言語処理系そのものの節目 に関するセッションと、AI 時代の型駆動開発や Agentic Coding 時代におけるテストの意義といった AI との向き合い方 を問うセッションが両軸で並んでおり、TypeScript 自身の進化と AI との関わり方の両方を同時に追えるラインナップでした。ブースで実施したアンケートでも「指示は出すが都度レビュー」「並列で動かしている」といった回答が並んでいたことから、参加者の AI 活用が着実に進んでいることも伺えました。 今後も TypeScript コミュニティ、TSKaigi がより一層発展していくことを期待しています。 今回の参加レポートが、TypeScript を学びたい・活用していきたい方の参考になれば幸いです。 運営の皆さん、カンファレンスを開催していただきありがとうございました!! アフターイベントのご案内 TSKaigi 2026 にスポンサーや登壇者で参加した ウェルスナビ・PeopleX・弁護士ドットコム・スリーシェイク・エブリー の 5 社で、2026年6月12日(金)に TSKaigi 2026 のアフターイベントを開催いたします! セッションや公募LTなどのコンテンツを用意しておりますので、みんなでTypeScriptで盛り上がりましょう! every.connpass.com 最後に エブリーでは、ともに働く仲間を募集しています。 テックブログを読んで少しでもエブリーに興味を持っていただけた方は、ぜひ一度カジュアル面談にお越しください! corp.every.tv 最後までお読みいただき、ありがとうございました!
はじめに こんにちは。RevComm でエンジニアをしている 林 です。 MiiTel Phone では長らく ESLint + Prettier をフロントエンドの lint / formatter として使ってきましたが、約 1,300 ファイル規模の React + TypeScript プロジェクトで CI と AI コーディングループのフィードバックがどうしても重くなってきていました。 今回、Rust 製ツールチェインである Oxc ベースの Oxlint / Oxfmt へ完全移行したので、その経緯と方法、得られたメリット・デメリットについて紹介します。 Oxc とは Oxc (The JavaScript Oxidation Compiler) は、パーサー・リンター (Oxlint)・フォーマッター (Oxfmt)・トランスパイラーといった JS/TS ツールチェインを Rust で書き直す OSS プロジェクトです。今回扱うのは次の 2 つ。 Oxlint: ESLint 互換のルールセットを持つ Rust 製リンター。 公式ドキュメント では ESLint 比 50〜100 倍速いとしている。JS Plugins (Alpha) と Type-Aware Linting (Alpha) も順次拡張中 Oxfmt: 2026-02 に Beta。Prettier v3.8 の JS/TS conformance 100% を達成し、出力互換を保ったまま高速化できる 開発主体は VoidZero Inc. — Vue.js / Vite の作者 Evan You 氏が 2024 年に設立したスタートアップです。Vite / Vitest / Rolldown / tsdown / Oxc を開発しています。 Biome ではなく Oxc を選んだ理由 似た立ち位置のツールに Biome がありますが、決め手は速度や Prettier 互換ではなく、周辺ツール全体を束ねる存在 ( Vite+ ) でした。 観点 Oxc (Oxlint / Oxfmt) Biome 開発主体 VoidZero (Vite 作者の会社) Biome コミュニティ 周辺ツールとの統合 Vite+ で build / test / lint / format が同じロードマップに乗る 独立型 規則互換性 ESLint ルール名 / Prettier 出力と互換重視 独自ルールセット中心 Vite+ は Vite / Vitest / Oxlint / Oxfmt / Rolldown / tsdown を vp という単一 CLI に束ねた統合ツールチェインです。 今回のプロジェクトも将来的に Vite+ への合流を視野に入れており、その時点で Oxlint / Oxfmt に乗っておいて良かったと言える状態を作りたかったのが Oxc 採用の主な動機です。 Biome に乗ると、将来 Vite+ に揃えたいときに lint / format をもう一度乗り換えるコストが再発します。 移行理由 1. CI / pre-commit / editor の速度がボトルネックになっていた 全件 lint で 13 秒、format チェックで 5 秒というのは、単体で見ればまだ我慢できる範囲です。ただし PR ごとに何度も走り、ローカルの保存時 lint や pre-commit hook でも体感に効いてくるため、合計すると無視できない時間になっていました。 2. AI コーディング時代のフィードバックループ Claude Code や Codex などの AI エージェントがほとんどのコードを書くようになると、lint は人間による最終レビューの代替手段から、 AI がコードを書く途中でその場でミスを弾いて修正する仕組み に役割を変えます。 具体的には、エージェントの編集ごとに lint と formatter を回し、エラーを次のターンに即フィードバックして自己修正させるという使い方です。 ESLint の 1 ファイルあたり 1.72 秒という所要時間ではこのループに乗せづらく、Oxlint の 0.43 秒で初めて現実的になりました。 AGENTS.md / CLAUDE.md の自然言語ガイドは読んだ後に指示が守られるかどうかはお祈りレベルの保証しかなく、絶対に止めたい違反はツール側で機械的に弾いた方が確実です。 lint にその役割を担わせる場合、速度がそのままループの成立可否になります。加えて、一度入れたルールはその後の全セッションに効くので、AI が書くコード量が増えるほど 1 ルールあたりの効果が積み重なっていきます。 3. 設定の簡素化と依存削減 ESLint 設定は eslint.config.js が 220 行、プラグインが 11 個ぶら下がっていました。Oxlint の native plugin で同等のカバレッジが取れるなら、設定もぐっとシンプルになります。 @eslint/compat のような互換レイヤーや、フォーマッター衝突回避のための周辺パッケージも整理できます。 4. Prettier 互換性が確保された Oxfmt が Prettier v3.8 互換 100% を達成したことで、フォーマッターを乗り換えると全ファイルに差分が出るという移行最大の壁が消えました。これがなければ移行コストが見合わなかったので、Beta リリースを待ってから着手する予定でした。 移行方法 Oxc は単一ツールではなく個別のツール群なので、Prettier → Oxfmt と ESLint → Oxlint をそれぞれ独立した PR で進めました。先に Oxfmt 側を済ませてから Oxlint に取りかかる順番です。 Prettier から Oxfmt 1. 差分ゼロ確認 まずローカルで src 配下の全ファイル (約 1,300 ファイル) について、 prettier --write の結果と oxfmt の結果が一致するか git diff で確認しました。 Beta とはいえ Prettier conformance テストが通っているので、現行コードに対しては実用上問題なしと判断しています。 2. 設定ファイル変換 Oxfmt は Prettier 設定からの自動変換コマンドを持っているので、これを使います。 npx oxfmt --migrate=prettier 生成された .oxfmtrc.json の例 ↓ { " $schema ": " ./node_modules/oxfmt/configuration_schema.json ", " semi ": true , " trailingComma ": " all ", " singleQuote ": true , " printWidth ": 120 , " tabWidth ": 2 } .prettierrc / .prettierignore は削除します。 3. scripts / hooks / codegen の置き換え // package.json (抜粋) { " scripts ": { " lint:oxfmt ": " oxfmt --check 'src/**/*.{ts,tsx,mdx}' ", " fix:oxfmt ": " oxfmt 'src/**/*.{ts,tsx,mdx}' " } } pre-commit hook の Prettier エントリも oxfmt に置き換え、 graphql-codegen の afterAllFileWrite や独自 codegen スクリプトで prettier --write を呼び出していた箇所も npx oxfmt に差し替えます。CI ワークフローのジョブ名もここで変更しました。 4. eslint-config-prettier はこの段階では据え置き 公式ドキュメント の推奨に従い、この段階では eslint-config-prettier をそのまま残しました。フォーマッタと衝突する ESLint ルール ( indent など) を無効化する役割は、Prettier でも Oxfmt でも変わらないためです。 なお ESLint 本体を撤去する後続の Oxlint 移行 PR で、同パッケージも併せて削除しています。 ESLint から Oxlint Prettier 移行と違ってこちらは挙動の互換性を見るだけではなく、ルールセットの取捨選択が必要です。方針として「native plugin 主軸、必要なものだけ JS Plugins (Alpha) で補う」を取りました。 1. 完全移行か併用か Oxlint 公式ドキュメントは「小〜中規模は完全置換、大規模は ESLint との併用を推奨」としています。併用したい場合は eslint-plugin-oxlint で Oxlint が拾えるルールを ESLint 側で重複排除し、両者を直列で回すのが定石です。 { " scripts ": { " lint ": " oxlint && eslint . " } } ちなみに今回は 1,300 ファイル規模ながら完全移行を選びました。主な理由は以下です。 速度メリットが消える: 全件 lint で oxlint 0.35s + eslint 12.96s ≈ 13.31s となり、現状とほぼ変わらず、AI ループ用 hook に乗せる速度を取り戻せない 設定の二重管理: eslint-plugin-oxlint で重複排除すると、独自設定 ( no-restricted-globals のカスタム設定など) が無効化されたり、既存の inline // eslint-disable-next-line ... が "Unused eslint-disable directive" として警告化されたりした ロードマップとの整合: 将来 Vite+ に揃えたい以上、併用は足がかりにもならない 「完全移行で破綻するほど大規模か」「速度を hook ループに取り戻したいか」が判断の分岐点で、以降のステップは完全移行ルートの内容です。 2. プラグインの仕分け 既存の ESLint プラグインを、native で代替可能 / JS Plugins で残す / 捨てる の 3 つに分類します。 既存の ESLint plugin 対応方針 typescript-eslint native typescript plugin react / react-hooks native react plugin (react-hooks 同梱) jest native jest plugin unused-imports typescript/no-unused-vars で代替 react-compiler eslint-plugin-react-hooks v6+ に統合されたため native でカバー testing-library / jest-dom oxlint jsPlugins (Alpha) 経由で src/__tests__/** 限定で残す storybook 撤退。 storybook build と Chromatic 視覚回帰で代替 import ( import/order ) oxfmt の sortImports に寄せる (後述) JS Plugins は ESLint プラグインをそのまま読み込めて便利ですが、Alpha かつ Oxlint の Rust native で速いという売りを部分的に削るレイヤなので、メインでは使いませんでした。 テスト系の 2 プラグインだけは「テスト信頼性に直接効くので確実に止めたい」「スコープが __tests__ 配下に閉じている」という条件が揃ったので、限定的に採用しています。 3. 自動変換 + 手調整 flat config からの自動変換は oxlint-migrate が使えます。 npx @oxlint/migrate <optional-eslint-flat-config-path> 生成された JSON に対して、native plugin で対応するルール名 ( @typescript-eslint/... → typescript/... など) の最終調整と、JS Plugins / overrides の追加を手で行います。最終的に出来上がった構成の骨子は次のような形です。 { " $schema ": " ./node_modules/oxlint/configuration_schema.json ", " plugins ": [ " typescript ", " oxc ", " react ", " jest " ] , " jsPlugins ": [ " eslint-plugin-testing-library ", " eslint-plugin-jest-dom " ] , " categories ": { " correctness ": " error ", " suspicious ": " error ", " pedantic ": " off ", " perf ": " off ", " restriction ": " off ", " style ": " off ", " nursery ": " off " } , " rules ": { // ... プロジェクト固有の制約ルール ... } , " overrides ": [ { " files ": [ " src/__tests__/**/*.{ts,tsx} " ] , " rules ": { " testing-library/no-node-access ": " error ", " jest-dom/prefer-in-document ": " error " // ... 他のテスト系ルール ... } } ] } plugins キーを書くと Oxlint のデフォルト plugin セットを上書きします。 typescript / oxc はデフォルトで有効ですが、明示的に書いておかないと意図せず外れる事故が起きやすいので注意が必要です。 もう一つ押さえておきたいのが categories と rules の関係です。 categories は Oxlint のルールを 7 種類 (correctness / suspicious / pedantic / perf / restriction / style / nursery) で一括 ON/OFF する仕組みで、今回は correctness (明らかなバグ) と suspicious (疑わしいコード) のみ error 、それ以外は off にしています。 優先順位は rules > categories なので、 categories.correctness: "error" で一括有効化したうえで、違反が多くて即修正できないルールだけを rules: { "no-extra-boolean-cast": "off" } で個別に逃がす、という使い方ができます。 次の「違反ルールは一時 off」はまさにこの優先順位を使った運用です。 4. 違反ルールは一時 off → follow-up で再有効化 Oxlint デフォルトの correctness カテゴリには、ESLint で off にしていたルールも一部入っています。本 PR で既存コードに手を入れない方針を取ったため、違反が出るルールは一度 off にしておき、別 PR で違反修正 + 再有効化する形にしました。 具体的には no-extra-boolean-cast , no-unneeded-ternary , no-useless-rename , no-useless-catch , jest/require-to-throw-message , react/jsx-key などです。1 PR にまとめると差分が膨大になって reviewer の負担になるので、ルールごとに小さく切り出すと進めやすかったです。 再有効化漏れを防ぐため、一時 off にしたルールはトラッキングチケットに「ルール名 / 違反件数 / 担当 / follow-up PR」のチェックリストで一覧化し、各 follow-up PR から逆リンクを張る運用にしています。 一方で oxlint-migrate の出力には Oxlint で未対応のルールも並びます。今回は @typescript-eslint/no-floating-promises のような type-aware 系 (本移行のスコープ外として別途段階導入する想定) と、 storybook/* のように代替手段ありで撤退と判断したものが中心でした。 未対応ルールは .oxlintrc.json に書いても効かないため、設定からは削除しています。 5. CI / pre-commit の切り替え // package.json (抜粋) { " scripts ": { " lint:oxlint ": " oxlint --deny-warnings src ", " fix:oxlint ": " oxlint --fix --deny-warnings src " } } Prettier から Oxfmt への移行段階で残していた eslint-config-prettier も含め、ESLint 関連 devDependencies はすべて削除しています。 6. import order は Oxfmt の sortImports に寄せる eslint-plugin-import の import/order ルールに相当する機能は Oxlint native にはありません。Oxfmt の sortImports で customGroups / groups を指定すれば、 pathGroups 相当のグループ分けが再現できるので、こちらに寄せました。 // .oxfmtrc.json (sortImports 部のみ抜粋) { " sortImports ": { " customGroups ": [ { " groupName ": " internal-modules ", " elementNamePattern ": [ " <your-dir-1>/** ", " <your-dir-2>/** " ] } ] , " groups ": [ " builtin ", " external ", " internal-modules ", [ " parent ", " sibling ", " index " ] , " style ", " unknown " ] , " newlinesBetween ": true , " order ": " asc " } } これにより lint と format の責務がきれいに分かれ、import の並び替えは Oxfmt の --fix で完結するようになりました。なお sortImports を有効化したコミットは全ファイルに差分が出るので、 .git-blame-ignore-revs に登録して git blame の汚染を防いでいます。 移行したことによるメリット 効果を数字でまとめると次の通りです。 項目 移行前 (ESLint / Prettier) 移行後 (Oxlint / Oxfmt) 倍率 全件 lint (約 1,300 ファイル) 12.96s 0.35s 約 37 倍 全件 format チェック 5.2s 0.5s 約 10 倍 単一ファイル lint 1.72s 0.43s 約 4 倍 設定行数 (lint) eslint.config.js 約 220 行 + 11 プラグイン .oxlintrc.json 約 80 行 — 数字には表れない次のような効果もありました。 単一ファイル lint が hook に乗る速度に収まり、AI エージェントの編集ループ内で常時走らせられるようになった Claude Code の PostToolUse hook で確実に止められる品質チェックをかけられるようになり、エージェントが見逃すエラーを一段減らせた ESLint 10 への移行で必要になる @eslint/compat のような互換レイヤが不要になった devDependencies が ESLint 本体 + 11 プラグイン分減り、 npm install 時間と node_modules サイズが改善 import order を Oxfmt 側に寄せたことで、lint と format の責務分離がクリアになった 移行したことによるデメリット JS Plugins が Alpha なので、 testing-library / jest-dom を残している部分は互換性が壊れるリスクを抱えている type-aware ルール ( no-floating-promises など) は速度トレードオフで今回意識的に lint 層から外している。Alpha 卒業待ちというより、現状の type-aware は 1 ファイルあたり数秒オーダーで hook ループのミリ秒単位の速さに載らないという判断。該当する欠陥は tsc と統合テストでカバーし、lint 層の速度を死守する方針 ESLint / Prettier に比べてコミュニティが小さく、トラブル時の情報量が少ない さいごに 今回の移行は単なる lint / formatter の乗り換えではなく、AI のフィードバックループそのものを速くするための改善でした。lint を hook ループに載せられるか否かが、AI が書くコードの品質に直接効いてくる時代になっています。 次に視野に入れているのは Type-Aware Linting です。たとえば no-floating-promises は AI が書いたコードで起きがちな await 漏れを検出できるルールで、 tsc だけでは取りこぼしやすく本来 lint で止めたい類の問題です。ただし現状の type-aware lint は速度的にまだ重く、hook の高速ループには乗せづらいため、当面は tsc とテストでカバーしつつ常時 hook には組み込まない方針でいく予定です。 AI 時代のフロントエンド開発環境として、これからもフィードバックループ全体の最適化を続けていきたいと思います。 参考文献 Oxc - The JavaScript Oxidation Compiler VoidZero - The Future of JavaScript Tooling Vite+ - The Unified Toolchain for the Web Oxlint Linter Guide Oxlint Configuration Oxlint JS Plugins (Alpha) Oxlint Type-Aware Linting Oxfmt Beta announcement (2026-02-24) Migrate from Prettier - Oxc oxlint-migrate (GitHub) eslint-plugin-oxlint (GitHub)
みなさんこんにちは!ワンキャリアのプロダクト開発部 ワンキャリア転職チームの越川(X: @kosshii_ )です。 突然ですが、みなさんは最近技術書を読んでいますか?
動画
該当するコンテンツが見つかりませんでした







