TL;DR
- 手元の 4 プロジェクトで
tsc --noEmitを測ると、TypeScript 5.7.3 に対して 7.0.2 は 2.16〜4.37 倍でした(社内データ、中央値、Apple M4)。10 倍という数字を目にしていたなら、手元ではそこまで出ないという結果です。 - 縮んだ絶対時間は最大でも 1.3 秒でした(
社内データ)。本ブログのリポジトリの CI は content-ci の実行時間が中央値 52 秒(社内データ、完了 run 6 件)で、そもそも型チェックが入っていません。「CI が何分縮むか」という問いには、この規模では「分では縮まない」が答えです。 - 速度より先に効いたのは診断の差分でした。同じソース・同じ tsconfig で、報告されるエラーの件数と種類が変わります。
- そして重要なのは、その差分が 7 ではなく 6 で入っていることです。本ブログのリポジトリでは 5.7.3 が 6 件、6.0.3 が 37 件、7.0.2 も 37 件でした(
社内データ)。5 から 7 へ一気に上げると、6 由来の変化を「ネイティブ移植のせい」と誤診します。 - 観測した差分は 3 つです。(1)
typesを書いていないと@types/*が自動で入らなくなる、(2) 5 で出ていたTS1170が 6 以降で出ない、(3) 型エラー時の終了コードが 5/6 の2から 7 では1に変わる。 - 第 7 節に比較スクリプトを置きました。型チェック対象 0 件は
exit 4、依存未インストールはexit 5で落ちます。逆に.d.tsしか拾わないincludeと@types未解決(TS2591)は検出しません。何を捕まえて何を捕まえないかを、実際に走らせて列挙してあります。
はじめに:速くなる話の前に、同じ結果が出るかを確かめる
コンパイラを上げる作業は、速度の話として始まって、互換性の話として終わります。tsc を差し替えたら CI が速くなった、しかし通っていたビルドが落ちる、あるいは落ちていたはずのものが通る。後者のほうが厄介です。
この記事は一般論ではなく、2026-09-09 時点で手元の 4 プロジェクトを実際に測った記録です。基準となるコミットは dc2aabf(2026-09-09)で、数値はすべてその時点のものです。「こう移行すべきだ」という組織向けの提言はしません。
型システム側の変化については TypeScript 5の型システム実務影響、CI 時間そのものの分解については CI遅延を分解して半分にする方法 で扱っています。本記事は**コンパイラ差し替え時に測るべき 2 つの量(時間と診断)**に絞ります。
1. 測定条件(機材・バージョン・基準 SHA)
再現できない数字は測定ではないので、条件を先に置きます。以降 社内データ と付した数値は、すべてこの条件で筆者が実測した値です。
| 項目 | 値 |
|---|---|
| 機材 | Apple M4 / 10 コア / macOS 26.6.2 |
| Node.js | v26.0.0 |
| 比較対象 | [email protected] / [email protected] / [email protected] |
| コマンド | tsc -p <tsconfig> --noEmit |
| 集計 | 連続 5〜7 回実行して wall time の中央値。1 回目(ディスクキャッシュが冷えている実行)も母数に含む |
| 対象 A | 本ブログのリポジトリ(Remotion + スクリプト、pnpm、基準 SHA dc2aabf)。型チェック対象は 29 ファイル |
| 対象 B | zod タグ v3.25.76 の src/v4 配下(テストを除く 75 ファイル、npm、依存は @types/node@24 のみ) |
| 対象 C / D | 生成した合成コード 500 / 3000 ファイル(型が単純で、外部依存なし) |
ファイル数は tsc --listFilesOnly の出力のうち、絶対パスで始まり node_modules と typescript/lib を含まない行を数えた実際に型チェックされた件数です。リポジトリ内の .ts ファイル数ではありません。
--listFiles ではなく --listFilesOnly を使うのは、--listFiles は診断も同じ標準出力に混ぜるからです。最初はこれを知らずに --listFiles | grep -v ... | wc -l で数え、エラー行をファイル数に足して対象 A を 45 件と書いていました。実際は 29 件です。数え方を1行で書ける形にしておかないと、こうして自分で作った数字を検算できません。
各バージョンは互いに干渉しないよう別ディレクトリへ入れました。6.0.3 も入れます。 後述のとおり、差分が入るのは 6 だからです。
mkdir -p /tmp/tsbench/ts5 /tmp/tsbench/ts6 /tmp/tsbench/ts7
cd /tmp/tsbench/ts5 && npm init -y >/dev/null && npm i [email protected]
cd /tmp/tsbench/ts6 && npm init -y >/dev/null && npm i [email protected]
cd /tmp/tsbench/ts7 && npm init -y >/dev/null && npm i [email protected]
/tmp/tsbench/ts5/node_modules/.bin/tsc --version # Version 5.7.3
/tmp/tsbench/ts6/node_modules/.bin/tsc --version # Version 6.0.3
/tmp/tsbench/ts7/node_modules/.bin/tsc --version # Version 7.0.2
TypeScript 7 は typescript パッケージ本体として配布されています。リリースは v7.0.2(2026-08-20 公開、GitHub API で確認)で、npm の typescript の latest も 7.0.2 でした(いずれも 2026-09-09 取得)。ネイティブ移植の開発用だった microsoft/typescript-go は API 上 archived: true になっており、@typescript/native-preview の latest は同日時点で 7.0.0-dev.20260707.2 で止まっています。プレビュー配布ではなく typescript@7 を測るのが正しい対象です。リリースノートは公式アナウンス Announcing TypeScript 7.0 を参照先として示しています。

2. 実測:型チェックは 2.16〜4.37 倍
社内データ(中央値、秒)。
| 対象 | 型チェック対象 | 5.7.3(min–max) | 6.0.3 | 7.0.2(min–max) | 5→7 | 試行 |
|---|---|---|---|---|---|---|
| A: 本ブログのリポジトリ | 29 | 1.436(1.216–1.646) | 1.169 | 0.350(0.302–0.577) | 4.10 倍 | 7 |
B: zod src/v4 | 75 | 1.683(1.605–1.880) | 1.655 | 0.385(0.367–0.417) | 4.37 倍 | 7 |
| C: 合成コード | 500 | 0.734(0.629–0.863) | 0.668 | 0.208(0.189–0.246) | 3.53 倍 | 5 |
| D: 合成コード | 3000 | 1.671(1.556–1.737) | 3.052 | 0.773(0.632–0.994) | 2.16 倍 | 5 |
読み取れることは 3 つあります。
倍率はファイル数に比例しません。 最も倍率が高かったのは 75 ファイルの zod で、3000 ファイルの合成コードは最も低い 2.16 倍でした。合成コードは型が単純で、チェッカーではなくパース・バインドが支配的です。倍率は規模ではなく型の重さで決まると考えるほうが、手元の数字とは整合します。
絶対時間は秒単位です。 5.7.3 との差は、実プロジェクトで 1.09 秒(対象 A)と 1.30 秒(対象 B)、合成コードで 0.53 秒(C)と 0.90 秒(D)でした。最大でも 1.3 秒です。
下限側の 2.16 倍は試行 5 回の値です。 min–max を併記したのはそのためで、対象 D は 5.7.3 が 1.556–1.737、7.0.2 が 0.632–0.994 とばらつきます。同じ行の 6.0.3 はさらに荒く(最小 1.888 / 最大 3.997)、中央値 3.052 を確定値として扱えないので、この行の 6→7 倍率は本記事の主張の根拠に使っていません。「2.16〜4.37 倍」というレンジは、下限側が n=5 由来であることを含めて読んでください。
3. 「CI は何分縮むか」への答え
対象 A(本ブログのリポジトリ)の GitHub Actions を API で数えると、完了 run の実行時間の中央値はこうでした(社内データ、2026-09-09 取得)。
| workflow | 中央値 | 観測 run 数 |
|---|---|---|
| content-ci | 52 秒 | 6 |
| article-risk-gate-check | 31 秒 | 6 |
| readme-sync-check | 23 秒 | 7 |
| spec-sync-check | 23 秒 | 8 |
観測 run 数は 6〜8 件しかないので、これは傾向であって確定値ではありません。そのうえで言えるのは、仮に型チェックを content-ci に足しても、7 にすることで消える時間は 1 秒程度で、52 秒の 2.1%(1.086 ÷ 52)だということです。基準 SHA dc2aabf 時点では pnpm test に tsc が含まれていないので、現状の型チェック時間は 0 秒です。
型チェックが CI の支配項になっている構成(数千ファイル規模のモノレポ、型チェック専用ジョブが分単位)でなければ、7 の速さを CI 短縮として回収するのは難しいというのが手元の数字から言えることです。ローカルの反復では 1 回あたり 1 秒強が消えるので体感は変わりますが、そちらは測っていません(第 8 節)。
4. 差分①:@types が自動で入らなくなる
ここからが、速度より先に出た変化です。3 ファイルだけの最小再現を作りました。
mkdir -p /tmp/repro && cd /tmp/repro
printf 'export const dir = process.cwd();\n' > a.ts
printf '{"name":"repro","private":true,"type":"module"}\n' > package.json
printf '{"compilerOptions":{"target":"ES2022","module":"nodenext","moduleResolution":"nodenext","strict":true,"skipLibCheck":true,"noEmit":true},"include":["*.ts"]}\n' > tsconfig.json
npm i -D @types/node@24
types フィールドを書いていない、ごく普通の tsconfig です。@types/node を 24 系にしたのは対象 A のリポジトリの指定(^24.0.0)に合わせたためです。Node.js 本体は v26.0.0 で動かしていますが、ここで見ているのは実行時ではなく型解決なので、両者が揃っている必要はありません。結果はこうなりました(社内データ)。
| コンパイラ | 結果 |
|---|---|
| 5.7.3 | exit 0(エラー 0 件) |
| 6.0.3 | exit 2 — TS2591: Cannot find name 'process'. |
| 7.0.2 | exit 1 — 同上 |
@types/node はインストール済みで、node_modules/@types/node も存在します。同じ再現を pnpm で作っても結果は同じでした。パッケージマネージャの配置の問題ではなく、バージョンによる挙動の違いです。
手当ては 1 行で、types を明示すると 5.7.3 / 6.0.3 / 7.0.2 の 3 つとも exit 0 になります。
{ "compilerOptions": { "types": ["node"] } }
対象 A のリポジトリでも同じ差が出ています。エラーコード別に数えると、5.7.3 が TS2322 5 件 + TS7016 1 件の計 6 件、6.0.3 と 7.0.2 はそこに TS2591 29 件と TS7006 2 件が加わった計 37 件でした(社内データ)。増えた 31 件のうち 29 件は「types に node を書け」という同一原因で、コードの型が壊れたわけではありません。移行前に types を明示しておけば、この 29 件は判断対象から外せます。
5. 差分②:5 で出ていたエラーが 6 以降で出ない
逆方向、つまりエラーが消えるケースもありました。
declare const key: string;
export type Computed = { [key]: number };
社内データ。
| コンパイラ | 結果 |
|---|---|
| 5.7.3 | exit 2 — TS1170: A computed property name in a type literal must refer to an expression whose type is a literal type or a 'unique symbol' type. |
| 6.0.3 | exit 0(出力なし) |
| 7.0.2 | exit 0(出力なし) |
対象 B より広い範囲(zod の src 全体、テストとベンチマークを含む 813 診断が出る構成)を測ったときも、5.7.3 だけが TS1170 を 3 件報告し、7.0.2 は報告しませんでした。同じ現象です。
これが意図的な仕様変更なのか副作用なのかは、手元の観測だけでは判定できていません。関連しそうな Pull Request は追えましたが、この再現に一致すると確認できたものはありません。ここで言えるのは「同じソースに対して 5 と 6/7 で結果が変わる」という観測だけです。移行の判断材料としては、原因の特定を待たずに自分のコードベースで差分を取るほうが速いというのが結論です。
なお、この差分も 5→6 で入っています。6.0.3 と 7.0.2 は、4 プロジェクトすべてでエラー件数もエラーコード集合も一致しました(社内データ)。ただし件数が 0 でないのは対象 A だけ(37 件)で、B / C / D は 3 バージョンとも 0 件です。「6 と 7 が一致する」の実質的な観測は対象 A の 1 件と、第 4 節・第 5 節の最小再現だと読んでください。
6. 差分③:終了コードが変わる
見落としやすい差分です。
社内データ。
| 状況 | 5.7.3 | 6.0.3 | 7.0.2 |
|---|---|---|---|
| 型エラーあり | 2 | 2 | 1 |
| tsconfig のパス誤り(TS5058) | 1 | 1 | 1 |
| 対象ファイル 0 件(TS18003) | 2 | 2 | 2 |
set -e や if ! tsc; then で見ている限りは影響しません。壊れるのは終了コードの値で分岐しているスクリプトです。if [ "$?" -eq 2 ] で「型エラーだけを許容する」ような処理を書いていると、7 では条件に入らなくなります。
上の 3 状況ではいずれも 0 が返りませんでした。設定が壊れていても対象が 0 件でも非ゼロで落ちています。ただしこれは観測した 3 つについての話で、あらゆる異常系でフェイルクローズだと確かめたわけではありません。
7. 自分のリポジトリで測るスクリプト
比較スクリプトで一番危ないのは、測れていないのに「差分なし」と表示することです。tsconfig のパスを打ち間違えた、include が何にもマッチしていない、依存を入れる前に走らせた。いずれも「エラー 0 件どうしが一致」に見えます。
#!/usr/bin/env bash
# TSC_OLD=<bin> TSC_NEW=<bin> ./ts7-compare.sh <tsconfig.json> [runs]
set -Eeuo pipefail
PROJECT="${1:?usage: TSC_OLD=<bin> TSC_NEW=<bin> $0 <tsconfig.json> [runs]}"
RUNS="${2:-5}"
: "${TSC_OLD:?TSC_OLD is required}"
: "${TSC_NEW:?TSC_NEW is required}"
[ -f "$PROJECT" ] || { echo "FATAL: tsconfig not found: $PROJECT" >&2; exit 3; }
"$TSC_OLD" --version >/dev/null 2>&1 || { echo "FATAL: not executable: $TSC_OLD" >&2; exit 3; }
"$TSC_NEW" --version >/dev/null 2>&1 || { echo "FATAL: not executable: $TSC_NEW" >&2; exit 3; }
case "$RUNS" in
''|*[!0-9]*|0) echo "FATAL: runs must be a positive integer: $RUNS" >&2; exit 3 ;;
esac
command -v /usr/bin/time >/dev/null 2>&1 || { echo "FATAL: /usr/bin/time not found" >&2; exit 3; }
WORK="$(mktemp -d)"; trap 'rm -rf "$WORK"' EXIT
# 対象ファイルが 0 件のまま「差分なし」を出さないための検査。
# --listFiles ではなく --listFilesOnly を使う。--listFiles は診断も同じ stdout に混ぜるため、
# エラー行をファイル数として数えてしまう。診断行は絶対パスで始まらないので ^/ に限定する。
LISTED="$("$TSC_OLD" -p "$PROJECT" --noEmit --listFilesOnly 2>/dev/null || true)"
INPUTS="$(printf '%s\n' "$LISTED" | grep -E "^/" | grep -cvE "/node_modules/|/typescript/lib/" || true)"
[ "$INPUTS" -gt 0 ] || { echo "FATAL: no project files were type-checked (inputs=0)" >&2; exit 4; }
diagnostics() { # $1=binary $2=outfile
local out
out="$("$1" -p "$PROJECT" --noEmit --pretty false 2>&1)" || true
case "$out" in
*"error TS18003"*|*"error TS5058"*|*"error TS5083"*)
echo "FATAL: project did not type-check anything:" >&2; echo "$out" >&2; exit 4 ;;
esac
printf '%s\n' "$out" | grep -E "error TS[0-9]+" | sort > "$2" || true
}
# 診断を先に取る。壊れた設定で計測ループを RUNS 回まわしてから落ちるのを避ける
diagnostics "$TSC_OLD" "$WORK/OLD.diag"
diagnostics "$TSC_NEW" "$WORK/NEW.diag"
# 依存が入っていないと両者とも同じ大量エラーを出して「一致」する。これを合格にしない
UNRESOLVED_OLD="$(grep -c "error TS2307" "$WORK/OLD.diag" || true)"
UNRESOLVED_NEW="$(grep -c "error TS2307" "$WORK/NEW.diag" || true)"
if [ "$UNRESOLVED_OLD" -gt 0 ] || [ "$UNRESOLVED_NEW" -gt 0 ]; then
echo "FATAL: module-not-found (old=${UNRESOLVED_OLD} new=${UNRESOLVED_NEW}). deps first" >&2
exit 5
fi
elapsed() {
/usr/bin/time -p "$1" -p "$PROJECT" --noEmit >/dev/null 2>"$WORK/t" || true
awk '/^real/{print $2}' "$WORK/t"
}
median() { sort -n | awk '{v[NR]=$1} END {print (NR%2) ? v[(NR+1)/2] : (v[NR/2]+v[NR/2+1])/2}'; }
measure() { # $1=binary $2=label
: > "$WORK/$2.times"; i=0
while [ "$i" -lt "$RUNS" ]; do elapsed "$1" >> "$WORK/$2.times"; i=$((i + 1)); done
median < "$WORK/$2.times"
}
OLD_MED="$(measure "$TSC_OLD" OLD)"
NEW_MED="$(measure "$TSC_NEW" NEW)"
echo "inputs : $INPUTS files (runs=$RUNS)"
echo "old : $("$TSC_OLD" --version) median ${OLD_MED}s diagnostics $(wc -l < "$WORK/OLD.diag" | tr -d ' ')"
echo "new : $("$TSC_NEW" --version) median ${NEW_MED}s diagnostics $(wc -l < "$WORK/NEW.diag" | tr -d ' ')"
awk -v o="$OLD_MED" -v n="$NEW_MED" 'BEGIN{ if (n+0 == 0) print "speedup: n/a"; else printf "speedup: %.2fx\n", o/n }'
if diff -u "$WORK/OLD.diag" "$WORK/NEW.diag" > "$WORK/diag.diff"; then
echo "verdict: diagnostics identical"
else
echo "verdict: DIAGNOSTICS DIFFER"; sed -n '3,$p' "$WORK/diag.diff"; exit 1
fi
実行するとこうなります。
$ TSC_OLD=/tmp/tsbench/ts5/node_modules/.bin/tsc \
TSC_NEW=/tmp/tsbench/ts7/node_modules/.bin/tsc ./ts7-compare.sh tsconfig.json 5
inputs : 75 files (runs=5)
old : Version 5.7.3 median 2.28s diagnostics 0
new : Version 7.0.2 median 0.46s diagnostics 0
speedup: 4.96x
verdict: diagnostics identical
落ちることを確かめた異常系
上 4 行は前提条件の検査で、コードを読めば結果が決まります(測定ではありません)。下 4 行は実際に tsc を走らせた結果で、こちらが 社内データ です。出力は実行時のものをそのまま書いています。
| 与えた状況 | 終了コード | 出力 | 種別 |
|---|---|---|---|
| tsconfig のパス誤り | 3 | FATAL: tsconfig not found: tsconfig.jsonx | 前提条件の検査(自明) |
| バイナリ不在 | 3 | FATAL: not executable: /nonexistent/tsc | 前提条件の検査(自明) |
runs が abc / 0 | 3 | FATAL: runs must be a positive integer | 前提条件の検査(自明) |
/usr/bin/time 不在 | 3 | FATAL: /usr/bin/time not found | 前提条件の検査(自明) |
| 正常(診断一致) | 0 | verdict: diagnostics identical | 実行結果 |
| 診断に差分あり | 1 | verdict: DIAGNOSTICS DIFFER と差分行 | 実行結果 |
include が 0 件マッチ | 4 | FATAL: no project files were type-checked (inputs=0) | 実行結果 |
| 依存未インストール | 5 | FATAL: module-not-found (old=240 new=237). deps first | 実行結果 |
落ちないと確かめたもの(限界)
ここを書かずに「空振り合格しません」と言うと、それ自体が空振りの主張になります。次の 3 つは検出しません。
includeが.d.tsにしかマッチしない構成。 入力件数としては 1 以上になるのでinputs=0の検査を通り、診断 0 件どうしが一致してexit 0になります。型定義だけを指しているのに合格に見えます。@typesが引けていない状態(TS2591)。 依存欠落の検査はTS2307(モジュール解決の失敗)だけを見ています。第 4 節の症状はTS2591なので、この検査には引っかかりません。ただし比較する 2 バージョンが 5 と 6/7 をまたぐなら、そこは診断の差分として検出されます。tsc -bが必要な構成。 project references の解決用 tsconfig(files: []+references)を-pで指すと参照先が型チェックされないので、この場合はinputs=0としてexit 4で落ちます。落ちますが、測れているわけではありません。
--pretty false を付けているのは、色付き出力が混ざると diff が差分だらけになるからです。診断は行単位でソートしてから比較するので、報告順の違いでは誤検出しません。
8. この測定の限界
- 1 台のマシンの 1 セッションの測定です。CI ランナー(共有 vCPU、ディスク I/O が異なる)で同じ倍率が出る保証はありません。
- 対象は 4 つで、うち 2 つは合成コードです。実プロジェクトは 29 ファイルと 75 ファイルの 2 つだけで、モノレポ規模の観測はありません。
- 合成 3000 の 6.0.3 は分散が大きく、確定値として扱っていません。
TS1170の消失について、原因となった変更は特定できていません。- 測ったのは
tsc -p <tsconfig> --noEmitのみです。エディタの応答性、--watch、--buildのインクリメンタル、エミットを含む挙動は測っていません。incrementalは 4 対象とも無効です。 - project references を使うリポジトリでは前提が変わります。
files: []+referencesの解決用 tsconfig を-pで指すと、そもそも参照先が型チェックされません。その構成ではtsc -bで測る必要があり、本記事の数字も第 7 節のスクリプトもそのまま当てはめられません(スクリプト側は「対象 0 件」としてexit 4で落ちます)。
まとめ
手元で測る限り、TypeScript 7 は確かに速く(2.16〜4.37 倍、社内データ)、しかし縮む絶対時間は秒でした。移行作業のコストを決めたのは速度ではなく、5→6 で入った診断の差分です。
ここまでの観測から導ける順序は、まず types を明示して 5 のまま通し、次に 6 へ上げて診断の差分だけを潰し、最後に 7 へ上げる、という分割です。7 への差し替えを最後に単独で置けば、そこで差分が出たときに原因が 1 つに絞れます。5 から 7 へ一度に飛ぶと、6 由来の変化をネイティブ移植の不具合として調べることになります。なお、この順序を実プロジェクトの移行として通しで実行した記録はまだありません。ここにあるのは 4 プロジェクトの測定結果と、そこから決めた作業順です。
同種の「更新に追随する順序」の設計は Node.js更新を48hで備える と Coding Agentのモデル更新と回帰テスト でも扱っています。共通しているのは、速くなったか / 新しくなったかではなく、前と同じ結果が出るかを先に測るという点です。
FAQ
TypeScript 7 は本当に 10 倍速いのですか
本記事は公表値の検証ではなく手元の測定です。そのうえで、4 プロジェクトの実測は 2.16〜4.37 倍でした(社内データ、中央値)。倍率はファイル数ではなく型の重さに依存し、型が単純な合成コード 3000 ファイルでは最も低い 2.16 倍でした。自分のコードベースで測ることを勧めます。
CI 時間はどれくらい縮みますか
本ブログのリポジトリの規模では 1.09 秒で、content-ci の中央値 52 秒の 2.1% でした(社内データ、観測 run 6 件)。型チェック専用ジョブが分単位でかかっている構成でなければ、CI 短縮としては回収しにくいです。
5 から 7 へ一度に上げてよいですか
手元では 5→6 で診断の差分が入り、6→7 では 4 プロジェクトすべてでエラー件数・コード集合が一致しました(社内データ)。一度に上げると、6 由来の変化を 7 のせいと誤診します。段階を分けたほうが切り分けが速くなります。
移行前に必ず確認すべき設定はありますか
types フィールドです。書いていないと 6.0.3 / 7.0.2 で @types/* が自動で取り込まれず TS2591 が出ます。"types": ["node"] のように明示すると 5 / 6 / 7 の 3 つとも同じ結果になりました。
CI スクリプトで壊れるところはありますか
型エラー時の終了コードが 5 / 6 の 2 から 7 では 1 に変わります。set -e や真偽での分岐は影響を受けませんが、終了コードの値で分岐しているスクリプトは条件に入らなくなります。
