トランスパイル
Transpiler ・ とらんすぱいる
新しい構文や TypeScript で書いたコードを、構文木を書き換えて古い環境でも動く JavaScript に変換すること。
✦ 30秒で試す — 航海書いたコードと、ブラウザが実際に動かすコードは別物だ変換した出力のうち、Promise を使う行は IE11 で動く?予想してみる概要
トランスパイルは、あるプログラミング言語のソースコードを、別の言語(あるいは同じ言語の別の版)のソースコードへ変換することです。Web 開発ではほぼ、「新しい構文や TypeScript のような拡張言語で書いたコードを、ブラウザが確実に実行できる JavaScript へ変換すること」を指します。代表的な道具が Babel と TypeScript のコンパイラ(tsc)で、近年は Go 製の esbuild や Rust 製の SWC・Oxc といった高速な実装も広く使われています。
コンパイラとの違いは変換の「高さ」です。コンパイルは一般にソースコードを機械語やバイトコードのような低い水準の形式へ落としますが、トランスパイルは同じくらいの抽象度のソースコード同士の変換です(trans + compile の合成語)。とはいえ中身の仕組みはコンパイラの前半とほぼ同じで、両者の境目は厳密なものではありません。開発者は最新の言語機能や型システムの恩恵を受けて書き、実行環境には枯れた JavaScript を届ける — この「書く言語と動かす言語を分ける」ことを成り立たせる工程がトランスパイルです。
なぜ生まれたか
JavaScript には、ほかの言語にない特殊な制約があります。実行環境が利用者のブラウザなので、開発者が処理系の版を選べないことです。サーバで動かす言語なら「本番環境の処理系を上げる」で済みますが、ブラウザは利用者のものです。言語仕様(ECMAScript)に便利な構文が入っても、古いブラウザを使う人がいる限り、そのまま書けば一部の環境で構文エラーになり、ページ全体が動きません。2015 年の ES2015 でクラス・アロー関数・モジュール・const / let など大量の構文が一度に入ったとき、この「仕様とブラウザ実装のずれ」は深刻になりました。
そこで生まれたのが「新しい構文で書き、配る前に古い構文へ機械的に書き換える」という解決です。Babel(2014 年、当初の名は 6to5)は ES2015 のコードを ES5 相当へ変換し、開発者が「ブラウザの普及を待たずに」新しい構文を使える道を開きました。同じ頃に広まった TypeScript(2012 年公開)は、型注釈という JavaScript にない機能を足した言語を、トランスパイルで JavaScript に落とすやり方で、「言語そのものを拡張したい」という要求にも同じ手法が効くことを示しました。先行した CoffeeScript などを含め、「実行環境は変えられないが、書く言語は変えられる」という発想の転換がトランスパイルの原点です。
詳細
変換は構文木の書き換え
トランスパイラの中身は、古典的なコンパイラの前半と同じ構造です。まずソースを字句解析・構文解析して AST(抽象構文木。コードの意味の構造を木の形のデータにしたもの)を作ります。x ** 2 なら「BinaryExpression(演算子 **)の下に Identifier x と NumericLiteral 2」という木です。次に、木をたどりながら変換のプラグインを当て、最後に木からコードを印字し直します。
変換は、木の中の特定の形のノードを見つけて、同じ意味の別のノードに差し替える作業です。x ** 2 を ES2016 より古いブラウザ向けにするなら、BinaryExpression を「Math の pow を x と 2 で呼ぶ CallExpression」に置き換えます。オプショナルチェーン user?.name は「user == null ? void 0 : user.name」という条件式のノードに、const は var に置き換わります。こうした変換は機能ごとに小さなプラグインに分かれているので、Babel の preset-env は「どのブラウザに対応するか」の指定(browserslist)から、そのブラウザが知らない構文の変換だけを選んで当てます。対応するブラウザを新しくするほど変換は減り、出力は短く速くなります。
実際にターゲットのブラウザを古くして、木のどのノードが置き換わるかを確かめてください。入力の行を押すとその文の木が出て、ノードを押すと入力と出力の対応する部分が光ります。
入力(行を押すと、その文の木を右に出す)
出力(木から印字し直したもの)
ポリフィル(構文ではなく、機能を足すコード)
AST(1行目の文)
上の目盛りのつまみを左へドラッグして、対応するブラウザを古くしてください。右の木のノードが古い構文のノードに置き換わり、出力コードが書き換わります。
構文とポリフィル — 役割分担
トランスパイルで解決できるのは「構文」の問題だけです。Promise や fetch、Array.prototype.includes のような新しい組み込みのオブジェクトや関数が古い環境に存在しない問題は、構文を書き換えても解決しません。上の部品で Promise のノードが最後まで変わらなかったのはこのためです。こちらを担うのがポリフィル — 欠けている機能を JavaScript 自身で実装して補う追加のコード(core-js が代表)です。新しい書き方は構文変換で、新しい機能はポリフィルで、と役割が分かれていて、両方をそろえて初めて古い環境で動きます。「変換したはずなのに古いブラウザで動かない」の大半は、この区別で説明できます。
TypeScript と「型の消去」
TypeScript のトランスパイルには特徴的な性質があります。型注釈は実行時には一切残らず、変換のときにすべて取り除かれることです(型の消去)。つまり型チェックはあくまでビルド時の検査で、実行される JavaScript に型の保証は含まれません。外から来るデータ(API の応答など)が型定義どおりである保証はどこにもないので、境界では実行時の検証(スキーマのチェック)が要る、という点は TypeScript 実務の大事な落とし穴です。
型を消すだけなら、型を理解する必要はありません。木から型注釈のノードを取り除けば済むので、プロジェクト全体の型を調べずに、1 ファイルずつ高速に変換できます。これが esbuild や SWC のようなツールが成り立つ理由で、「変換は高速なツールで、型チェックは tsc を別に走らせる」という分業が定番になりました。
ただし例外があります。enum や値を持つ namespace、コンストラクタの引数に private を付けて自動でプロパティにする書き方は、型ではなく実行時に動く JavaScript を生成する TypeScript 独自の構文です。const enum のように、ほかのファイルの型情報を見ないと正しく変換できないものもあります。1 ファイルずつ変換するツールでも安全に変換できるコードだけを書くよう tsc に確かめさせるのが isolatedModules オプションで、さらに TypeScript 5.8(2025 年)の erasableSyntaxOnly は「型を消すだけで JavaScript になる構文」以外をエラーにします。Node.js は 23.6 から .ts ファイルの型を消すだけでそのまま実行できるようになり(型ストリッピング)、この機能もまさに消せる構文しか扱いません。TypeScript は「型を足した JavaScript」に近づく方向へ整理されつつあり、2026 年 7 月には型チェッカ自体を Go で書き直した TypeScript 7 が出て、別に走らせる型チェックの遅さも大きく縮まりました。
ソースマップ — 変換後の世界でデバッグする
トランスパイルには副作用があります。ブラウザで実際に動いているのは変換後のコードなので、エラーの行番号やデバッガの表示が、自分の書いたコードと一致しなくなるのです。これを解決するのがソースマップで、変換後コードの各位置が元のコードのどこに対応するかを記録したファイルです。トランスパイラは木を印字し直すときに、各ノードが元のソースのどこから来たかを覚えているので、この対応表を作れます。ブラウザの開発者ツールはこれを読み、あたかも元の TypeScript や最新の構文のまま実行しているかのようにデバッグさせてくれます。トランスパイルは多くの場合バンドラのビルドの中で実行されるので、複数の変換を経てもソースマップが正しく連鎖するよう、各ツールが対応表を引き継いでいます。
試問
画面の上に、ターゲットにするブラウザの年の目盛りがあり、つまみを左へ動かすほど古いブラウザ向けに変換されます。下には入力のコード、変換後の出力、そして入力の1行ぶんの構文木(AST)が並び、木のノードを押すと入力と出力の対応する部分が光ります。ES2016 までの構文しか知らないブラウザに向けて、コードを配る場面です。
入力(行を押すと、その文の木を右に出す)
出力(木から印字し直したもの)
ポリフィル(構文ではなく、機能を足すコード)
AST(1行目の文)
上の目盛りのつまみを左へドラッグして、対応するブラウザを古くしてください。右の木のノードが古い構文のノードに置き換わり、出力コードが書き換わります。
