---
title: tscからtsgoへ ── DenoのTypeScript基盤はどう変わったか
tags: 
author: [Yusuke Tanaka](https://image.docswell.com/user/magurotuna)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/V7PK3N3PJ8.jpg?width=480
description: https://2026.tskaigi.org/talks/2
published: May 22, 26
canonical: https://image.docswell.com/s/magurotuna/59NRVP-tskaigi-2026
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/V7PK3N3PJ8.jpg)

TSKaigi 2026 · 2026-05-22
Denoの
TypeScript基盤は
どう変わったか
tsc
から tsgo へ ――――
Yusuke Tanaka a.k.a. maguro


# Page. 2

![Page Image](https://bcdn.docswell.com/page/2JVV4K4VJQ.jpg)

まず、今日見る現象
スライドURL
// main.ts
import { basename } from &quot;@std/path/basename&quot;; // import map → JSR
import chalk from &quot;npm:chalk@5&quot;;
// npm specifier
const bytes: string = await Deno.readFile(&quot;./data.bin&quot;); // TS2322
await Deno.connect({ hostname: &quot;example.com&quot;, port: 80 });
// deno.json
{ &quot;imports&quot;: { &quot;@std/&quot;: &quot;jsr:@std/&quot; } }
$ deno check main.ts
TS2322: Type &#039;Uint8Array&#039; is not assignable to type &#039;string&#039;.
今日見るのはコードを実行する deno run ではなく、deno check（型チェック）側


# Page. 3

![Page Image](https://bcdn.docswell.com/page/5EGL1Q11JL.jpg)

LSP も
VS Code の赤線・hover も、今日話す 型チェック / 診断 / LSP 側の話
スライドURL


# Page. 4

![Page Image](https://bcdn.docswell.com/page/4JQYDKDN7P.jpg)

このトークでの用語
スライドURL
用語
このトークでの意味
V8 Isolate
ops
V8 の独立した実行環境。Denoが実行を制御
V8 isolate 内の JS から Deno Rust 側へ戻る callback
TypeScript checker / language service 本体
tsc を Go で再実装したもの
Deno専用 fork ではなく、npm から typescript package として配布されるもの
TypeScript で書かれたプログラムを実行する
deno run したときに通るパス。
deno check したときに通るパス。
TypeScript の型チェックを行う
import x from &quot;y&quot; の &quot;y&quot; 部分
tsc
/ typescript-go
stock TypeScript (from npm)
実行パス
診断パス
(import) specifier
tsgo


# Page. 5

![Page Image](https://bcdn.docswell.com/page/K74WZPZ3E1.jpg)

なぜ実行パスと診断パスの分離が重要？
Deno のソースには、stock TypeScript だけでは決められない情報が混ざる。
import { basename } from &quot;@std/path/basename&quot;; // import map -&gt; jsr:@std/...
import chalk from &quot;npm:chalk@5&quot;;
// npm specifier
// Deno namespace
await Deno.readFile(&quot;data.bin&quot;);
await Deno.connect({ hostname: &quot;example.com&quot;, port: 80 });
診断パスでは、これらを TypeScript が扱える形へ渡す必要がある
• specifier / import maps → Deno の resolver / module graph で解決
• Deno.readFile などの Deno API → Deno の lib 型定義として注入
• diagnostics → Deno 側の元ソース位置へ戻して表示
一方、実行パスは、型注釈をswcで剥がし、specifierを解決してからJSとしてV8に渡す
tsc , tsgo は介在しない
スライドURL


# Page. 6

![Page Image](https://bcdn.docswell.com/page/LJ1YR9RZEG.jpg)

Timeline — 実行から診断へ、境界は外へ
古い Deno
2020
〜
〜
2022-04 06
2025-10 2026
が型チェックも担っていた
`deno run --no-check` で型チェックを skip 可能に (#6456, #6895)
`deno check` 追加、`deno run` 等は no-check default へ (#14072, #14691)
forked `tsgo` 実験 → Go版 fork infra 削除 → stock 方向へ
`deno run`
最初からきれいに分かれていたわけではない。
実行時の変換と、型チェック / 診断 / LSP の境界がだんだん分離され、外へ動いた。
スライドURL


# Page. 7

![Page Image](https://bcdn.docswell.com/page/GJWG1Q1672.jpg)

今日の見取り図
スライドURL
このトークで見るのは、Deno が TypeScript checker / language service を
どこに置き、Deno固有の module world とどう接続してきたか。
Phase 1
Phase 2
Phase 3
の中
Deno binary の外
Deno project の外
Deno binary
embedded `tsc` (V8 isolate)
forked `typescript-go` subprocess
stock TypeScript from npm


# Page. 8

![Page Image](https://bcdn.docswell.com/page/4EZLPWPR73.jpg)

スライドURL
Phase 1
embedded tsc in V8 isolate


# Page. 9

![Page Image](https://bcdn.docswell.com/page/Y76WMGM17V.jpg)

embedded tsc ベースのアーキテクチャ
┌──────────────┐
request: graph/options
┌─────────────────────────┐
│
Deno Rust
│ ══════════════════════════▶
│
│
CLI / LSP
│ ◀════ ops: fs/resolve/libs ═ │
JsRuntime / V8 isolate │
99_main + 97_ts_host
│
└──────┬───────┘
│
└───────────┬─────────────┘
│ loads
▼
▼
┌──────────────────────┐
│ module graph / cache │
┌─────────────────────────┐
│ patched `tsc`
│
│ npm / JSR / imports
│ (00_typescript.js)
│
└──────────────────────┘
│
└─────────────────────────┘
を V8 isolate の中で動かし、
ファイル読み込み・モジュール解決などを ops 経由で Deno Rust 側に戻す。
• Rust 側: module graph, resolver, cache, etc. を握る
• JS 側: TypeScript の Program オブジェクトを組み立てて、diagnostics / LSP 応答を返す
tsc
スライドURL


# Page. 10

![Page Image](https://bcdn.docswell.com/page/G75MZVZL74.jpg)

DenoがTypeScriptに見せている世界
側の概念
に見せる形
Deno
TypeScript
import
jsr: package
npm: package
import maps
Deno globals
cache-backed source / virtual file name
JSR resolver が選んだ package file / 型情報
mode に応じて global cache / ローカル node_modules / BYONM など
specifier 解決結果、または tsconfig.json の paths 相当の対応表
Deno.readFile などの型定義を含む *.d.ts
https://...
にパッチを当てたり、アダプタ層を設けてDeno Rust側と必要な情報をやり取りできるようにすることで、
TypeScript側がDeno独自の世界を正しく解釈できるようにしている
tsc
スライドURL


# Page. 11

![Page Image](https://bcdn.docswell.com/page/9J29R3R3ER.jpg)

Phase 1 を振り返ると
Denoの内部に、tsc を抱え込んでいた 状態。
うまく回った点
• tsc 本体に近い挙動を保ちやすい
• module graph / resolver / libs を Deno 側で強く制御できる
コスト要因
• patched tsc ( 00_typescript.js ) は約 8.7 MiB（release build では zstd 圧縮して同梱）
• 上流 tsc への追随コスト — release ごとに fork に patch を cherry-pick → build → rsync する半手動運用
• tsc 用 runtime / isolate を作り、request を実行する設計上のコスト
• ただし、実行時のコストは CLI snapshot + V8 code cache により最適化されている
• さらに LSP の文脈では open files / unsaved edits / hover / rename / cancellation / diagnostics lifecycle と
いった複雑性が加わる
スライドURL


# Page. 12

![Page Image](https://bcdn.docswell.com/page/DEY4D1D8JM.jpg)

スライドURL
Phase 2
forked tsgo subprocess


# Page. 13

![Page Image](https://bcdn.docswell.com/page/VJNY626978.jpg)

Deno forked tsgo experiment
denoland/deno#30920
· MERGED · 2025-10-20
feat(unstable): typescript-go integration for deno check
• --unstable-tsgo フラグで有効化される experimental feature
• 対象は deno check（型チェック側だけ）
• 使ったのは公式の typescript-go ではなく、denoland/typescript-go fork
• 理由: Deno固有の概念（ http(s): , jsr: , npm: , import maps, etc.）を tsgo に渡す経路が無かった
fork は始めるのに便利。
ただ、上流の TypeScript 本体との 差分 (fork drift) を背負うことになる
スライドURL


# Page. 14

![Page Image](https://bcdn.docswell.com/page/YE9PL1L3J3.jpg)

補足: tsgo によるパフォーマンス改善度合いは？
スライドURL
計測: Deno 2.7.11 / 7 runs median
project
denoland/std
dsherret/dax
scope
deno check
(44 entrypoints)
deno check mod.ts
あくまで簡易 bench。Microsoft が公表する一般 benchmark の倍率とは別物
embedded tsc
--unstable-tsgo
speed-up
3.39 s
1.09 s
1.30 s
0.43 s
2.60x
2.56x


# Page. 15

![Page Image](https://bcdn.docswell.com/page/GE8DX4XLED.jpg)

subprocess + stdio IPC
スライドURL
┌──────────────┐
┌──────────────────────┐
│
│ typescript-go --api
Deno Rust
│ ═══ request: graph/options ═══▶
│ CLI / LSP
│ ◀══ callbacks: resolve/... ════
└──────────────┘
│
│ (child process)
│
└──────────────────────┘
wire: MessagePack over stdio
(protocol adapted from `libsyncrpc`)
tsgo は Deno固有の specifier や module graph をそのままでは解決できない。
そこで型チェック中、tsgo が Deno 側に聞きに来る callback を足した。
• モジュールパスの解決 (jsr/npm/URLを実ファイルへ)
• 型定義ファイルの参照解決
• module の format 判定 (CJS / ESM)
境界の形は変わった ―― Rust ↔ V8 ↔ tsc から、Rust ↔ child process ↔ tsgo へ。
ただし、Denoの世界を翻訳する仕事 は消えていない


# Page. 16

![Page Image](https://bcdn.docswell.com/page/LELM868Q7R.jpg)

LSP はどうするか？
スライドURL
の次は LSP (エディタ補完)。ここでも tsgo へ移す試みが続いた。
ただ、両者は扱う形が大きく違う。
deno check
形
主な仕事
状態
キャンセル
エディタ)
deno check
LSP (
1回起動して終わる
ファイルを読んで型エラーを返す
なし
不要
エディタが開いている間ずっと動く
補完・hover・rename を、編集に追随して返し続ける
プロジェクト全体を持続的に保持
必須 (タイプを追えないと使い物にならない)
は「API を1回呼ぶ」で済むが、LSP は エディタとの双方向 protocol を tsgo に被せる必要がある
加えて、テスト・運用インフラそのもののコスト:
• 旧 tsc server と tsgo server の両方を切り替えて走らせる test harness が要る
• 切り戻し可能な状態を保つ branch 運用も、地味に重い
deno check


# Page. 17

![Page Image](https://bcdn.docswell.com/page/4JMY65XKJW.jpg)

スライドURL
Phase 3
fork
を捨てて、公式 npm パッケージへ


# Page. 18

![Page Image](https://bcdn.docswell.com/page/PJR9P3N679.jpg)

転換点 — PR #33133
denoland/deno#33133
· MERGED · 2026-04-02 · net
スライドURL
約 5,000 行削減
remove forked typescript-go infrastructure
状態
消えた
残っている
残っている（埋め込み）
( tsgo fork) と、それを支えるインフラ
denoland/TypeScript ( tsc fork)— 2026-05-05 に 6.0.3 へ更新 (#32944)
cli/tsc/00_typescript.js 約 8.7 MiB の source bundle は今も残る
denoland/typescript-go


# Page. 19

![Page Image](https://bcdn.docswell.com/page/PEXQ34NDJX.jpg)

公式 TypeScript (npm) へ寄せる方向
stock TypeScript = npm install typescript で得られる、Microsoft 公式のパッケージ。
fork を持たず、これを Deno が直接使えるようにする方向。
ここに辿り着くまでに、いくつかの試みがあった:
PR
#33146
#33160
#33163
status
CLOSED
MERGED → REVERTED (#33162)
OPEN
stock tsgo --lsp proxy案。Deno 固有事情の解決が壁に
deno tsconfig という独立 subcommand 案
deno install で tsconfig を生成 ← 現在のlive direction
スライドURL


# Page. 20

![Page Image](https://bcdn.docswell.com/page/3EK9YZNDED.jpg)

PR #33163: stock TypeScript への翻訳
stock TypeScript 向けに、Deno の依存と型をローカル生成する案
つまり、tsc や tsgo にパッチをせず、すでにある仕組みを活用した翻訳を試みる
JSR packages
Remote modules
jsr:
を npm.jsr.io から取り、
node_modules/@jsr/... に展開
http(s):
Deno globals
tsconfig bridge
を生成し、
typeRoots + types: [&quot;deno&quot;] で明示読み込み
に paths を生成し、
root tsconfig.json から extends
node_modules/@types/deno/index.d.ts
を CliFileFetcher で取得し、
.deno/remote/... に mirror
.deno/tsconfig.json
設計の肝:
stock TypeScript が読めるローカル構造へ materialize する。
jsr: / npm: / https: と Deno globals を、
moduleResolution: &quot;bundler&quot; は、: 入り specifier を受けるための妥協点
スライドURL


# Page. 21

![Page Image](https://bcdn.docswell.com/page/L73W9DVP75.jpg)

その他、ユーザーから見える変化
診断パス側の変化がすでに Deno main に取り込まれている
// before
const id: number = setTimeout(() =&gt; {}, 100);
clearTimeout(id);
// Deno main (#33823, lib.node default)
const id: NodeJS.Timeout = setTimeout(() =&gt; {}, 100);
clearTimeout(id); // NodeJS.Timeout | number | undefined
• lib.node が デフォルトで有効 に (#33823, 2026-05-05)
• WebGPU 型は lib.dom 経由に統一 (Deno独自 lib.deno_webgpu は削除)
TypeScript checker から見える Deno の世界を、公式 TypeScript と同じ形 に揃えた
スライドURL


# Page. 22

![Page Image](https://bcdn.docswell.com/page/87DKGP83JG.jpg)

つまり、Phase1,2,3の全体の流れは
Phase1（embedded tsc ）は、tsc にパッチを当て、V8 Isolate 内で実行
2. Phase2（fork tsgo ）は、
境界が1個外に動いた
• V8 isolate 内の tsc → forked tsgo をサブプロセスで立ち上げ、IPC で通信
3. tsgo fork ベースのアーキテクチャは取り下げられた
• LSP 対応のコスト
• そもそも fork を維持しつづけることのメンテナンスコスト
4. そこで Phase3 stock TypeScript from npm を使う方針
• Deno固有の世界を fork なしで どう stock TypeScript と統合するか
• deno install + generated tsconfig 案 (#33163) はまだ open
1.
スライドURL


# Page. 23

![Page Image](https://bcdn.docswell.com/page/VJPK3N8PE8.jpg)

スライドURL
他プロジェクトのアプローチ


# Page. 24

![Page Image](https://bcdn.docswell.com/page/2EVV4KNVEQ.jpg)

Oxlint × tsgolint
Oxlint (Rust) で no-floating-promises のような 型を見る lint rule (type-aware lint) を実装したい場合
→ oxc-project/tsgolint を子プロセスで起動し、その中で type-aware lint を実行
しかし、tsgo の checker / AST / scanner は internal/ に閉じ込められている（非公開）
直接 tsgo API を呼び出している
tsgolint は tsgo の非公開 API を公開化する hack を適用し、
詳細: syumai「tsgolintはいかにしてtypescript-goの非公開APIを呼び出しているのか」
スライドURL


# Page. 25

![Page Image](https://bcdn.docswell.com/page/57GL1QK1EL.jpg)

Vize ── 爆速 Vue toolchain
@ubugeeei による Rust 製の Vue toolchain (vizejs.dev)
bench: 15,000 SFC を 12-core で 373ms ／ type check は vue-tsc 比 8.9x
tsgo
の使い方
入力 .vue 群
が SFC を分解し、&lt;script lang=&quot;ts&quot;&gt; + テンプレート相当の TS を合成
仮想 .ts / .tsx / .d.ts (この段階では in-memory で保持)
↓ tsgo に渡す経路に入ったときだけ `materialize()` を呼ぶ
実際のファイルシステムに書き出し + tsconfig.json を生成
↓
Vize
↓
tsgo
を子プロセスで起動（普通の TypeScript project として読ませる）
ubugeeeiさんにXで質問し、回答をいただきました。ありがとうございます！
スライドURL


# Page. 26

![Page Image](https://bcdn.docswell.com/page/4EQYDKNNJP.jpg)

Biome
スライドURL
oxlint、Vize は本家 TypeScript checker を借りる方向。
Biome はその真逆 ── 本家を呼ばず、自前の Rust 製 semantic model で完結する範囲だけを扱う
parser → semantic model
↓
常に走る: scope / references)
(
有効な rule に `RuleDomain::Types` 持ちのものが
├─ 無ければ → そのまま rule 評価
└─ あれば
→ module graph を walk して
各ファイルで type inference を実行
(fast path)
(TypeAware mode)
(biome_js_type_info crate)
• 利点: Rust binary 一つで完結、subprocess / IPC / JIT warmup なし
• 妥協: TS compiler 完全互換ではない、深い型機能 (Conditional / Mapped 等) は追わない
• TypeAware mode で動く rule の例: useArraySortCompare , useAwaitThenable ほか


# Page. 27

![Page Image](https://bcdn.docswell.com/page/KJ4WZPG371.jpg)

スライドURL
まとめ


# Page. 28

![Page Image](https://bcdn.docswell.com/page/LE1YR9DZ7G.jpg)

まとめ
スライドURL
Deno は TypeScript checker / LSP を
Deno binary の中 → Deno binary の外 → Deno project の外 へと押し出してきた
TypeScript checker
Oxlint
Vize
Biome
Deno Phase 3
との橋渡し
子プロセス + tsgo 非公開API hack
仮想 TS を node_modules に materialize
本家を呼ばず、自前 inference で完結
jsr/npm/https/types をローカル生成 + tsconfig で橋渡し
tsgolint
Vize と Deno Phase 3 には似た設計圧がある ──「fork も再実装も避けたい」制約のもとで、stock checker が読める
形へ materialize する方向へ寄っていく


# Page. 29

![Page Image](https://bcdn.docswell.com/page/GEWG1QY6J2.jpg)

Who am I
スライドURL
• Deno Land Inc.で Deno Deploy、Deno Sandbox, Claw Patrol をやって
います 🦕
• 最近の興味はDeterministic Simulation Testing, Zig, Networking
• 米ジョージア工科大学コンピュータサイエンス専攻修了（2025年12月）
GitHub: magurotuna
Yusuke Tanaka a.k.a.
maguro
𝕏: @yusuktan
LinkedIn: yusuktan


