Upgrade to Pro — share decks privately, control downloads, hide ads and more …

TSKaigiSendai2026.pdf

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest. →
Avatar for Saji Saji
November 01, 2026
86

 TSKaigiSendai2026.pdf

Avatar for Saji

Saji

November 01, 2026

Transcript

  1. 原則:型は付随情報 実行時やbuild時にTSの型情報は消える • tsc や esbuild は型注釈を消すだけ。 • Node も型注釈を消して

    TS を実行する(type stripping) • 型エラーがあっても、tsc は同じ JS を出力する • JSON.parse(s) as Foo の Foo が間違っていても、実行結果は変わらない
  2. 型でビルド結果を変えるツールがある イメージ 型なし最適化 元コード class a { class Cart {

    型あり最適化 class a { constructor() { constructor() { items: string[] = []; this.items = []; this.h = []; count = 0; this.count = 0; this.g = 0; add(item: string) { this.items.push(item); } } add(c) { add(c) { this.count++; } } this.items.push(c); this.h.push(c); this.count++; this.g++; } } } }
  3. Closure Compiler Google Closure Compiler Gmail や Google マップのような大規模な JavaScript

    アプリケーションを開 発するために、Google が作った最適化コンパイラ。2009年公開 • 2009年当時そこまで一般的でなかった モジュール解決 / JSDoc の注釈での 型 / 最適化を含むビルドを独自の形でサポートする意欲的なツールだった • Closure Libraryなどと一緒に公開され利用されてた(※) • 豆知識 : JSのSourceMapの仕様はClosure Compiler発 / 今でもReactの buildの中で使われてたりする ※ClosureLibraryの方は現在EOLになっている😭(弊社は一部まだ使ってる)
  4. Closure Compiler Google Closure Compiler のビルド最適化 3段階の最適化レベルを持ってる • WHITESPACE̲ONLY :

    空白除去のみ • SIMPLE : ローカル変数のリネームなど、一般的な mini er 相当 • ADVANCED : プログラム全体を解析し、プロパティ名のリネーム、関数のイ ンライン化、到達しないコードの削除まで fi → 今回の話は ADVANCEDの時+型をちゃんと書いた場合の話
  5. Closure Compiler Google Closure Compiler での型 class Cart { constructor()

    { /** @type {!Array<string>} */ this.items = []; /** @type {number} */ this.count = 0; } /** @param {string} item */ add(item) { this.items.push(item); this.count++; } } 特徴 • JSDoc で書く • Closure 自身の型チェッカーが検査 • TSと色々違う • オブジェクトの型は既定でnull許容 • anyに似た ? 型 • /** @type {!X} */ (式) でキャスト
  6. Closure Compiler 型情報を使ってサイズを削減できる! 条件 サイズ SIMPLE 712,558 - ADVANCED (型を使わない)

    38,469 -94.6% ADVANCED (型を使う) 29,236 対SIMPLE 対型なし -95.9% ツール Raw Zip - Vite 9,249 3,442 - esbuild 9,273 3,485 -24.0% Closure ADVANCED 7,251 3,016 オプションによるサイズの違い 他ツールとの比較 ADVANCEDモードで型情報を使うとサイズをさらに削れる • (ADVANCEDモード単体も十分優秀だけど…)
  7. Closure Compiler 条件を破ると警告なしで壊れる class Cart { constructor() { /** @type

    {number} */ this.count = 0; } } const saved = /** @type {!Cart} */ JSON.parse('{"count": 41}'); console.log(saved.count); savedをCartだと信じる → Cartと同し形でプロパティを短縮できるはず fi → 実際のJSONにそんなプロパティはない!🤯 (unde nedで勝手に進む)
  8. scriptc scriptcの最適化戦略 関数ごとに、2種類のどちらかでコンパイルするか決める • static tier : 型から決めた表現でコンパイル (←こっちに入りたい!) •

    dynamic tier: 実行時に値の型を調べるネイティブコードにコンパイル static tierに入るためには? 1. 関数ごとに型のない値(dyn)を見つける 2. 1で見つけた型のない値を操作していないと言える → ちゃんと型書いておくのが大事!
  9. scriptc static tierでどのくらい速く・小さくなるのか 同じ処理で型付きの TS と、型注釈だけを取り除いた JS をビルドしてみた • 対照としてNodeでの実行も入れてる

    速度 型付き 型なし 型なし / Node(型 型付き なし JS) 数値計算 41 ms 1.63 s 40 倍 72 ms 数値計算 127 KB 487 KB 3.8 倍 Object操作 138 ms 870 ms 6.3 倍 370 ms Object操作 215 KB 616 KB 2.9 倍 型分岐操作 87 ms 9.4 倍 123 ms 型分岐操作 146 KB 580 KB 4.0 倍 818 ms サイズ 型付き 型なし 型なし / 型付き 条件 : Apple M4 Pro、macOS 26.6.2、scriptc 0.4.6Node v26.12.1
  10. scriptc 型でなぜ速く・小さくなるのか 型情報による最適化で速度に聞いていそうなのは以下 • オブジェクトを固定レイアウトで持てる : メモリの配置や読み込みで有利 • ネイティブ側の近い型に変換できる :

    number → double で持つ等 • union の分岐で実行時に型を調べる処理を省ける • ジェネリクスを型引数ごとに別のコードにするといったこともできる サイズに関しては「実行時の型判定コード」がいらなくなるのが大きい
  11. scriptc 型をどこまで信じるのか • 型チェッカーが推論した型(narrowing を含む) • 信じてメモリレイアウトの固定や分岐の省略に使う • as・!(narrowing)・.d.ts・JSON.parse などの結果

    • あくまで主張とみなし、値が型付きの側に入るところで検査する • any・型のないコード • dynamic tier で、実行時に型を調べる
  12. scriptc 型で嘘をつくとどうなるのか? interface Cart { count: number; } const saved

    = JSON.parse(process.argv[2]) as Cart; // ← error console.log(saved.count); 実行時エラーとして止まりどのパスで何が違うかエラーに出る • > TypeError: expected number at $.count, got string • アサーションが間違ってた際やany型を代入しようとした時も同様
  13. scriptc scriptc まとめ • scriptc は、型チェッカーが証明した型を信じ、主張された型は実行時に検査 する形で最適化 • 実は10/20 に型と合わない値でも実行を続ける変更(#1561)が入り、同じ

    日に revert された(#1601)。 • 理由: "scriptc's premise is that types don't lie” • 型が正しく書かれている場合は実行速度でもサイズでも強い最適化が入る • 一方 as などを使って「嘘をついている」とNode で動くプログラムが ̀TypeError̀ で止まることがある
  14. bun ̶aot Bunのbuildコマンドにおける --aot オプション Build時にTS/JS をビルド時に機械語へコンパイルし、実行ファイルを作るオプ ション、--aotが追加された • bun

    build --compile --aot のように実行する • --compile オプション単体だとJSファイルと実行ランタイムをまとめたフ ァイルになる • Scriptc同様にTSで書かれた型を使ってコンパイルの最適化を行う ※かなり実験的な機能なので発表時:2026/11/01以降大きく変わる可能性があります
  15. bun ̶aot Bunの --aot 時ののコンパイル TSをコンパイルする場合、関数ごとに次の3つをまとめて生成する • 入口の検査: 渡された値が型どおりかを調べる •

    型最適化版: 型どおりの値が来たときに使う • 汎用版: 型どおりでない値が来たときに使う 実行時にテストをし、値が型通りでなかったら汎用版にfallback • 型は外部からくる予測の型とフロー解析で確定した型に分けて管理
  16. bun ̶aot 型による最適化で速く・小さくなるのか…? 処理 命令数(型あり / 型なし) 一般的なイディオム(中央値) 1.00 小さなオブジェクトの

    JSON.parse 0.30 ループ内の分割代入 0.18 パッケージ 1 回の描画 0.96 結果 • 速度: JSON.parseのような特徴的な処理以外はそこまで速くならない • サイズ: むしろ型ありのほうが約 1 割大きくなる
  17. bun ̶aot 型でなぜそこまで速く・小さくならないのか メモリのレイアウト固定の大部分は型ではなくフロー解析で決まる • 型が効くのは、解析で追えない値がメイン • 性能比較で差が出た JSON.parse と分割代入がこれにあたる

    • 型は予測として扱うだけで入口の検査が必ず残る サイズに関してはむしろ不利 • 予測した型を使う関数は、本体に加えて入口のテストと汎用版を持つ
  18. bun ̶aot 型で嘘をつくとどうなるのか? 遅くなるだけで、結果は ̀bun ruǹ と同じになるようfallback interface Cart {

    count: number; } const saved = JSON.parse(process.argv[2]) as Cart; console.log(saved.count); // OK 型の不一致を記録するオプションにすると型違反が起きてることはわかる • > expected Cart, got untyped object; continued
  19. bun ̶aot Bunの--aot まとめ • Bun --aot は、型を予測として使い、外れたら汎用版で動くことで、実行結 果を型に依存させない •

    その代わりに型による最適化は境界部分に集まり、全体としては小さい。 • ※この辺りの設計は直近2週間で入れ替わってるので不安定 • 10/09 の投稿では as が値を変換し、JSON.parse の結果が型と合わなけ れば TypeError(scriptc に近い設計)だった • 10/22 にこれを「throw・変換しない」設計に変わった。
  20. これまでの3つを並べる 型で嘘をつくと 速度(型あり / 型なし) サイズ(型あり / 型なし) Closure 警告なしで壊れる

    基本同じ Closure 向けコードで -24%、普通の書き方で -7% scriptc 止まる(TypeError) 8〜35 倍速い 3〜4 分の 1 Bun --aot 遅くなるだけで、結果は同じ ほとんど同じ~少しだけ速い 約 1 割大きい tsc 何も起きない 同じ 同じ
  21. 型を信じるメリデメと得られるもの 傾向 : 型を信じるほど得られるものもリスクも大きい + 効くのはネイティブ メリット Native Transpile scriptc

    (速い&小さい) Closure Compiler (多少小さい) Bun ̶aot (少しだけ速い) tsc (変わらない) リスク 特になし 少しサイズ増 実行時エラー 警告なしで壊れる
  22. TSのネイティブコンパイルの潮流 TS/JS をネイティブや Wasm にコンパイルするツールが直近いくつか出ている • Static Hermes(Meta)の typed mode

    • 型が嘘にならないよう言語の意味側を変える(配列の範囲外アクセスは例外) • 使えるTSの記法は限られる • Por or ff • ClosureCompiler同様型を愚直に信じるタイプの最適化をおこなう