---
title: 制約と時代から読み解くTypeScriptコンパイラ設計史
tags: 
author: [Yoshiaki Togami](https://docswell.com/user/6114195)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/GJ5MZXKQJ4.jpg?width=480
description: tskaigi 2026 day2 https://2026.tskaigi.org/talks/38
published: May 23, 26
canonical: https://docswell.com/s/6114195/K8NM12-2026-05-23-103822-tskaigi
---
# Page. 1

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

T S K a i g i 2 0 2 6 DAY 2
#tskaigi_leverages
制約と時代から読み解く
TypeScript コンパイラ設計史
Yoshiaki Togami
2026 /05/23


# Page. 2

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

2025 03/11 typescript-go 爆誕
A 10x faster TypeScript — with Anders Hejlsberg
#tskaigi_leverages 2/52


# Page. 3

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

Why Go?
前提: rewriteではなくport
型システムが独特なのに仕様がないのでport必須
循環参照のデータ構造に強く依存している
AST Nodeへ後付け書き込みもある
既存の実装をそのまま移せてかつパフォーマンスが出せる言語が適している
Rustだとborrow checkerと噛み合わせが悪い
microsoft/typescript-go #411 / TypeScript is being ported to Go | interview with Anders Hejlsberg
#tskaigi_leverages 3/52


# Page. 4

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

Why Go?
ネイティブFirst
共有メモリ並行性
GC
循環参照を素直に持てる
Line by Lineで移植しやすい
Classをほぼ使わずClosureベース → Goのstruct + methodに機械変換が楽
ts-to-goという変換スクリプトで一括変換
microsoft/typescript-go #411 / TypeScript is being ported to Go | interview with Anders Hejlsberg
#tskaigi_leverages 4/52


# Page. 5

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

そもそも🧐
Rustとの相性以前にコンパイラとしては少し独特
なぜこうなっている？
循環参照のデータ構造に強く依存している
AST Nodeへ後付け書き込みもある
#tskaigi_leverages 5/52


# Page. 6

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

Agenda
01
02
03
04
速習 TypeScript Compiler
独特な内部設計
時代背景と JS の制約
Go port による改善
話さないこと
型システムそのものや checker.ts の実装
flowNodeなど制御フロー解析周りのこと
#tskaigi_leverages 6/52


# Page. 7

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

01
速習 TypeScript Compiler
#tskaigi_leverages 7/52


# Page. 8

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

TypeScript Compiler
Source
function add(
a: number,
b: number,
) {
return a + b;
}
add(1, &quot;2&quot;);
Scanner
Parser
FunctionKeyword
Identiﬁer
OpenParenToken
Identiﬁer
ColonToken
NumberKeyword
CommaToken
Identiﬁer
...
SourceFile
FuncDecl
Binder
ExprStmt
Ident(add)Params Block CallExpr
Return Args
...
...
SourceFile.locals
add
Symbol(add)
add.locals
a
Symbol(a)
b
Symbol(b)
Symbol(add):
Checker
Symbol → Type
a : number
b : number
add :
(a: number, b: number)
=&gt; number
name:
&quot;add&quot;
add(1,
flags:
Function
decls:
[Node]
Argument of type &#039;string&#039;
is not assignable to type
&#039;number&#039;.
→ FuncDecl in AST
&quot;2&quot; )
#tskaigi_leverages 8/52


# Page. 9

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

Source -&gt; Scanner -&gt; Parser
Source
function add(
a: number,
b: number,
) {
return a + b;
}
add(1, &quot;2&quot;);
Scanner
Parser
FunctionKeyword
Identiﬁer
OpenParenToken
Identiﬁer
ColonToken
NumberKeyword
CommaToken
Identiﬁer
...
SourceFile
FuncDecl
ExprStmt
Ident(add)Params Block CallExpr
Return Args
...
...
普通のコンパイラと同じく、tokenize → AST 生成 までは共通
#tskaigi_leverages 9/52


# Page. 10

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

Source -&gt; Scanner -&gt; Parser
ASTでは構文的に正しいが意味的にNGなケースは捕まえにくい
function add(a: number, b: number) { return a + b; }
add(1, &quot;2&quot;); // Argument of type &#039;string&#039; is not assignable to parameter of type &#039;number&#039;.
TypeScript に判断して欲しいのはセマンティクスエラーの方
AST だけでは判断できない → もう一工夫必要🤔
#tskaigi_leverages 10/52


# Page. 11

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

Symbol
識別子からそれが指している宣言や種類、スコープへとアクセスするための基本単位
どんな名前か
どこで宣言されたか、元の AST ノードへの参照 ( declarations )
どんな種類の宣言か ( BlockScopedVariable , Function , Class , etc.)
(ES6のSymbolとは別物)
#tskaigi_leverages 11/52


# Page. 12

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

Binder
ASTとSymbolを結びつけるコンポーネント
Symbol、SymbolTableの作成
flowNodeの構築
この段階では型は確定しない
全てCheckerによる遅延評価
#tskaigi_leverages 12/52


# Page. 13

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

Checker
AST と Symbol を使って型を計算・検査するコンポーネント
function add(a: number, b: number) { return a + b; }
add(1, &quot;2&quot;); // Argument of type &#039;string&#039; is not assignable to parameter of type &#039;number&#039;.
1. add という名前 → どの宣言か? を Symbol から引く
2. その宣言から パラメータの型 を計算 ( number , number )
3. 実引数の型 ( 1: number , &quot;2&quot;: string ) と 比較してエラー
#tskaigi_leverages 13/52


# Page. 14

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

02
独特な内部設計
#tskaigi_leverages 14/52


# Page. 15

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

問題は。。
コレ👇
Source
function add(
a: number,
b: number,
) {
return a + b;
}
add(1, &quot;2&quot;);
Scanner
Parser
FunctionKeyword
Identiﬁer
OpenParenToken
Identiﬁer
ColonToken
NumberKeyword
CommaToken
Identiﬁer
...
SourceFile
FuncDecl
Binder
ExprStmt
Ident(add)Params Block CallExpr
Return Args
...
...
SourceFile.locals
add
Symbol(add)
add.locals
a
Symbol(a)
b
Symbol(b)
Symbol(add):
name:
&quot;add&quot;
flags:
Function
decls:
[Node]
→ FuncDecl in AST
Checker
Symbol → Type
a : number
b : number
add :
(a: number, b: number)
=&gt; number
add(1,
&quot;2&quot; )
Argument of type &#039;string&#039;
is not assignable to type
&#039;number&#039;.
#tskaigi_leverages 15/52


# Page. 16

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

特徴その1 ASTが semantic情報を背負う
Binderが AST に直接後から書き込む
parent Nodeへのポインタも後から書き込む
node.symbol = symbol // Symbol
node.locals = ... // SymbolTable
node.flowNode = ... // CFA
node.parent = ... // Pointer to parent
構文木 + 意味解析要素が1つの可変ツリーに同居
immutableな層を持たず、parentごとこの1枚に直書きする
#tskaigi_leverages 16/52


# Page. 17

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

特徴その2 循環参照
ASTが親 &lt;=&gt; 子で循環参照
AST &lt;=&gt; Symbol間で循環参照
Symbol同士が親 &lt;=&gt; 子で循環参照
メリット
ASTから自分の親、子、Symbol、スコープなにアクセスができる
#tskaigi_leverages 17/52


# Page. 18

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

なぜこうなっている？
#tskaigi_leverages 18/52


# Page. 19

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

03
時代背景と JS の制約
#tskaigi_leverages 19/52


# Page. 20

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

周辺の出来事を見る🧐
2004年4⽉
2008年9⽉
Gmail 公開
2011年頃
V8 / Chrome
Erich Gamma が MS ⼊社
VS Code プレビュー
VS Online &quot;Monaco&quot;
2006年10⽉
2004
2015年4⽉29⽇
2013年11⽉
2014年
Docs &amp; Spreadsheets
Atom Shell / ピボット
2016
2016年4⽉
VS Code 1.0 GA
2005年2⽉
Google Maps
2009年5⽉
Node.js 初公開
2010年頃
内部開発開始 (Strada)
2012年10⽉
TS 0.8 公開 (GOTO)
2014年4⽉3⽇
TS 1.0 公開
#tskaigi_leverages 20/52


# Page. 21

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

2004 ~ 2006年
Google から Gmail, Google Map, Google Docsなどが公開
いわゆるAjax革命
Gmail (2004)
Google, &quot;Hitting send on the next 15 years of Gmail&quot;, The Keyword, 2019
Google Maps (2005)
Version Museum, &quot;22 Years of Google Maps Website Design History&quot;
(peterme.com, 2005)
#tskaigi_leverages 21/52


# Page. 22

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

2008年 - 2010年
2008年9月GoogleのV8の公開や
Chromeのリリース
IE7のおおよそ約15倍
JSエンジン戦争により高速化が進んだ
Node.jsの公開(2009)
JavaScript Performance Rundown
#tskaigi_leverages 22/52


# Page. 23

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

2010年 at Microsoft
IEのChakraもパフォーマンスを上げる
高速化によりJSでより大規模なソフトウ
ェアが書かれる流れ
MS内部でOfficeなどの製品のWeb移
植が迫られていた
TypeScript Origins: The Documentary（1m14s〜）
#tskaigi_leverages 23/52


# Page. 24

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

しかし、、
当時のJSツーリングは非常に貧弱
他言語では普通な開発体験が全くできない
Luccoは開発のためのエディタから作り始める
7000行あたりでのバグ追いに半日溶かす経験を繰り返す
#tskaigi_leverages 24/52


# Page. 25

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

アプローチ
1. JSを遠ざける
別言語で書いて JS にトランスパイル
Script#, CoffeeScript など
MS内部の一部チームでは Script# を使っており、当初はそれを採
用しようとした
2. JSを置き換える
Google の Dart
2011年10月発表 / 2013年11月 安定版リリース
#tskaigi_leverages 25/52


# Page. 26

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

第3の選択肢
ターゲット言語からそこまで離れるくらいなら、
JS自体を直すべきでは🧐
#tskaigi_leverages 26/52


# Page. 27

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

Strada 誕生
既存JSのスーパーセットとして型を足す
LuccoはTypeScriptの開発に専念
Editorは別のチームへ引き継がれる
Visual Studio Online &#039;Monaco&#039;
&#039;Monaco&#039; Workbench(内部用)
後の VSCode
VS Code Day Keynote with Erich Gamma (3:41~)
#tskaigi_leverages 27/52


# Page. 28

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

Dogfooding体制の確立
I made them agree to one thing, which is: when we have enough TypeScript
that you could actually use it, please try to use it in building VS Code and give us
feedback because we desperately need feedback.
TypeScript で VS Code を作る
VS Code で VSCode自身とTypeScript を作る
#tskaigi_leverages 28/52


# Page. 29

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

初期のMonaco Workbench(VSCodeの前身)
Behind The Scenes: The Making of VS Code (4:30~)
#tskaigi_leverages 29/52


# Page. 30

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

TypeScriptに課せられた要件
JavaScriptのスーパーセットであること
型が実行時に消えること
ツーリング/IDE体験を成立させること
#tskaigi_leverages 30/52


# Page. 31

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

Editor / IDE 独特の要件
ソースコードが頻繁かつ局所的に変わる
同じ木に対して複数の関心事(補完 / hover / 診断 など)が同時並行にアクセスする
応答要件
200ms超えると遅い
#tskaigi_leverages 31/52


# Page. 32

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

それを満たすための条件
1. イミュータブルである
サブツリー再利用
インクリメンタルパース
並行読み取り
2. 親へのアクセスができる
上向き操作
3. 位置情報、コメントなどのTriviaの保持
#tskaigi_leverages 32/52


# Page. 33

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

カーソル位置から文脈へ
エディタ操作の起点はカーソル位置のト
ークン / 小さな構文ノード
Hover: この位置は型情報は何？
Breadcrumb: 今どの構文階層にい
る？
Find All References: このSymbolは
他のどこに使われている？
#tskaigi_leverages 33/52


# Page. 34

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

親アクセスとImmutableの両立ができない
同じノードを複数の親で共有できない
部分木を再利用すると parent が旧ツリーを指す
構造共有もインクリメンタルパースも素直には書けない
immutability と親アクセスを両立する仕組みが要る
#tskaigi_leverages 34/52


# Page. 35

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

C# Roslyn
同じ Anders Hejlsberg が関わった C# コンパイラ Roslyn
従来コンパイラはバッチ処理のブラックボックス
Roslyn は中間解析結果に API でアクセスできる設計
複数スレッドから解析結果を共有
IDE統合前提
-&gt; そこで生まれたのが Red-Green Tree
dotnet/roslyn
#tskaigi_leverages 35/52


# Page. 36

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

red green tree(赤緑木)
rust-analyzer や swiftでも採用
Green Tree
parentを持たない、widthだけ持つ、純粋にimmutableで共有可能
Red Tree
Greenをラップして、parentと絶対位置を遅延計算で乗せる
&quot;木のImmutablityに保ちたい&quot;と&quot;文脈情報(親や絶対位置)をたどりたい&quot;を&quot;両立
#tskaigi_leverages 36/52


# Page. 37

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

赤緑木で全部解決！
#tskaigi_leverages 37/52


# Page. 38

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

ちょっとまった！
#tskaigi_leverages 38/52


# Page. 39

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

問題その1 immutability をあまり活かせない
2010年ごろのJS
ES5が出たばかり
ES6はこの5年後
Node.jsのバージョンは0.1 ~ 0.2
worker APIは皆無
#tskaigi_leverages 39/52


# Page. 40

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

問題その2 メモリ消費
RUST
pub struct GreenNode {
kind: u16, // 2 bytes
width: u32, // 4 bytes
}
// total 8 bytes(padding込み)
GO
type GreenNode struct {
kind uint16 // 2 bytes
width uint32 // 4 bytes
}
// total 8 bytes(padding込み)
C#
internal struct GreenNode {
public ushort Kind;
// 2 bytes
public int FullWidth; // 4 bytes
}
// total 8 bytes(padding込み)
#tskaigi_leverages 40/52


# Page. 41

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

JSの場合
class GreenNode {
constructor(
public kind: number, // 4 bytes
public width: number, // 4 bytes
) {}
} // ≈ 数十 bytes
オブジェクトには数十バイトのヘッダがつく
データレイアウトの制御が難しい
news.ycombinator.com/item?id=46719899
V8やChakraで試したけど合わなかった
おそらく現代と比べるとまだJITの最適化が未熟で試したけどあまり合わなかったと推察
#tskaigi_leverages 41/52


# Page. 42

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

その結果
永続データ構造としての immutability を捨てる
ツーリング体験の実現とその機能の応答速度優先 =&gt; メモリの消費は犠牲
親ポインタを持つ
incremental parseはもっと粗い粒度で実現
#tskaigi_leverages 42/52


# Page. 43

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

単一のAST + binder による段階的詰め込み
parse → AST → binder → checker
↓
parent / symbol / flowNode
を AST に直接貼り付ける
単一ツリーで構文と意味解析結果を兼ねる
symbol / locals / flowNodeなどを AST に直接貼り付ける
parent pointer は最初から（lazy 生成なし）
red-greenでいうred木のみ。greenのようなイミュータブルな層はない
実行時性能への影響がなくIRを別で最適化する動機も薄かった
#tskaigi_leverages 43/52


# Page. 44

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

JS環境に最適化してきたが限界が
パフォーマンス優先の実装なのに時間がかかるレベル😵
もちろんメモリの消費も多い
#tskaigi_leverages 44/52


# Page. 45

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

04
Go port による改善
#tskaigi_leverages 45/52


# Page. 46

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

なぜ Go で早くなる？
約 10x の内訳
3x: ネイティブ化
3x: 共有メモリ・マルチスレッド化
→ 合計 約 10x
TypeScript is being ported to Go | interview with Anders Hejlsberg
#tskaigi_leverages 46/52


# Page. 47

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

単一バイナリ
ランタイムのウォームアップに時間をショートカット
従来: tsc(JavaScriptで書かれたコンパイラ) -&gt; Node.jsプロセス(V8) -&gt; V8がJITコンパ
イル -&gt; 機械語を実行
Go: tsgoの単一バイナリ -&gt; 機械語を実行
#tskaigi_leverages 47/52


# Page. 48

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

値型
JS: 子ノードや属性が別オブジェクト
辿るたびにヒープ上のバラバラな場所へジャンプ -&gt; キャッシュミス
(ただし現代ならかなり最適化が入っている)
Go: struct をフィールドや配列にインラインで埋め込める
言語仕様レベルで連続配置が保証される
一回の読み取りで周辺の値が同じキャッシュラインに乗る
#tskaigi_leverages 48/52


# Page. 49

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

値型化
typescript-go/internal/ast/ast.go
type Node struct {
Kind
Kind
Flags NodeFlags
Loc
core.TextRange
id
atomic.Uint64
Parent *Node
data
nodeData
}
// 値（インライン）
// 値（インライン）
// struct を値で埋め込み
// 循環参照のためポインタは残る
スカラ・小さい struct は値で埋め込み
循環参照が必要なところはポインタ維持
#tskaigi_leverages 49/52


# Page. 50

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

共有メモリ・マルチスレッド
2010年くらいのJS(node.js)にworker APIはない
後にworker threads + SharedArrayBufferで共有+並列処理できるが、、
オブジェクトをそのまま渡せず煩雑
参照だらけの巨大なオブジェクトグラフを表現するのが現実的でない
#tskaigi_leverages 50/52


# Page. 51

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

共有メモリ・マルチスレッド
#tskaigi_leverages 51/52


# Page. 52

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

まとめ
時代の都合で出発点が既存のJSの拡張路線だった
JSで書くかつEditor/IDE統合を最初から念頭に置いた結果独特の仕組みになった
その独特な仕組みの上にトリッキーな型システムが構築されてきた
JS自体の制約から来ていた限界がGo Portで解消された
memory layout
並列処理
etc.
#tskaigi_leverages 52/52


