Source Map の仕組みとデバッグ活用法

本番環境にデプロイされる JavaScript は、ミニファイやバンドルによって元のソースコードとはまったく異なる姿になっています。エラーが発生しても、圧縮された 1 行のコードではどこに問題があるのか見当がつきません。Source Map はこの変換前と変換後のコードを対応づける地図のような存在であり、本番コードのデバッグを現実的なものにしてくれます。

変換されたコードの問題

モダンな JavaScript 開発では、ソースコードが本番環境に届くまでに複数の変換を経ます。TypeScript のトランスパイル、JSX の変換、バンドル、ミニファイ。これらの工程を経た出力は、人間が読める状態ではなくなっています。

// 開発時のコード
function calculateTotal(items) {
  const subtotal = items.reduce((sum, item) => {
    return sum + item.price * item.quantity;
  }, 0);
  const tax = subtotal * 0.1;
  return subtotal + tax;
}

これがミニファイされると次のようになります。

function calculateTotal(t){const e=t.reduce((t,e)=>t+e.price*e.quantity,0);return e+.1*e}

この圧縮コードの中で例外が発生した場合、ブラウザのエラーメッセージは「1 行目の 68 文字目」のような位置情報しか示しません。変数名も短縮されているため、どの処理で何が起きたのかを推測するのは困難です。

Source Map が解決すること

Source Map は、変換後のコードの各位置が変換前のどのファイルの何行目に対応するかを記録したファイルです。ブラウザの開発者ツールが Source Map を読み込むと、あたかも元のソースコードでデバッグしているかのように表示してくれます。

ミニファイされたコードでエラーが発生する

ブラウザが Source Map を参照し、元のファイル名と行番号を特定する

開発者ツールが元のソースコード上にエラー箇所を表示する

ブレークポイントの設定も元のソースコード上で行えるため、圧縮後のコードを意識する必要がなくなります。

Source Map ファイルの中身

Source Map は JSON 形式のファイルで、拡張子は .map です。たとえば bundle.js に対して bundle.js.map が生成されます。その中身を見てみましょう。

{
  "version": 3,
  "file": "bundle.js",
  "sources": ["../src/utils.ts", "../src/main.ts"],
  "sourcesContent": ["export function add(a, b)...", "import { add }..."],
  "names": ["add", "calculateTotal", "items"],
  "mappings": "AAAA,SAAS,IAAI,CAAC,CAAS..."
}
version

Source Map の仕様バージョン。現在は 3 が標準で、ほぼすべてのツールがこのバージョンを出力する。

sources

変換元のファイルパスの配列。バンドルに含まれる元ファイルがすべて列挙される。

sourcesContent

元ファイルの内容そのもの。これがあると、元のソースファイルがなくても開発者ツール上でコードを閲覧できる。

mappings

変換後の位置と変換前の位置の対応を Base64 VLQ でエンコードした文字列。Source Map の核心部分。

mappings のエンコーディング

mappings フィールドは一見すると意味不明な文字列に見えますが、明確な構造を持っています。セミコロンが出力ファイルの行の区切り、カンマが同一行内のセグメントの区切りを表し、各セグメントが「出力の列・元ファイルのインデックス・元の行・元の列・名前のインデックス」を Base64 VLQ という可変長エンコーディングで表現しています。

このBase64 VLQというエンコーディングは、位置情報を絶対値ではなく前のセグメントからの差分として記録します。差分は多くの場合小さい値になるため、ファイルサイズを大幅に圧縮できる仕組みです。

Variable-Length Quantity の略で、小さい数値は少ないバイト数、大きい数値は多いバイト数で表現する符号化方式。

たとえば mappings の先頭が AAAA だった場合、これは「出力の 1 行目 0 列目が、sources 配列の 0 番目のファイルの 0 行目 0 列目に対応する」という意味になります。すべてが 0 からの差分なので A(値 0 を表す)が 4 つ並びます。

各ビルドツールでの生成方法

主要なビルドツールはすべて Source Map の生成に対応しています。設定方法はそれぞれ異なりますが、基本的にはオプションをひとつ有効にするだけです。

Rollup では output.sourcemap を設定します。

// rollup.config.js
export default {
  input: 'src/main.js',
  output: {
    file: 'dist/bundle.js',
    format: 'iife',
    sourcemap: true
  }
};

Vite はデフォルトでは本番ビルドに Source Map を含めませんが、build.sourcemap で有効化できます。

// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  build: {
    sourcemap: true
  }
});

TypeScript コンパイラでは tsconfig.json に設定します。

{
  "compilerOptions": {
    "sourceMap": true
  }
}

複数の変換を経る場合(TypeScript → バンドル → ミニファイ)、各段階で生成された Source Map をチェーンとして連結する必要があります。Rollup や Vite はこのチェーン処理を内部で自動的に行ってくれるため、最終的な .map ファイルひとつで元の TypeScript ソースまで遡れます。

ブラウザ開発者ツールでの活用

Source Map を生成したら、ブラウザの開発者ツールでどう活用できるかを見ていきましょう。

バンドルファイルの末尾には次のようなコメントが付与されます。

//# sourceMappingURL=bundle.js.map

ブラウザはこのコメントを検出すると、自動的に .map ファイルを取得して解析します。Chrome DevTools の Sources パネルを開くと、バンドルされた 1 つのファイルではなく、元のディレクトリ構造とファイルが表示されます。

ブレークポイント

元の TypeScript や JSX ファイル上にブレークポイントを設定できる。実行はミニファイされたコード上で行われるが、停止時の表示は元のソースで行われる。

変数の確認

ミニファイで短縮された変数名(a、b、t など)ではなく、元の変数名(items、subtotal、tax など)でウォッチや評価ができる。ただし最適化の度合いによっては正確に復元できない場合もある。

コールスタック

エラー発生時のコールスタックが元のファイル名と行番号で表示される。「bundle.js:1:68」ではなく「utils.ts:15:8」のように読める形になる。

Source Map の種類と使い分け

Source Map にはいくつかの出力形態があり、用途に応じて選択できます。

外部 Source Map

bundle.js.map として別ファイルに出力する標準的な方式。sourceMappingURL コメントで参照先を指示する。本番環境では .map ファイルのアクセスを制限することで、ソースコードの露出を防げる。

インライン Source Map

Source Map の内容を Base64 エンコードし、JavaScript ファイル末尾に data URI として埋め込む方式。開発時に便利だが、ファイルサイズが大きく膨らむため本番には不向き。

Rollup では sourcemap: 'inline' を指定するとインライン方式になり、sourcemap: 'hidden' を指定すると .map ファイルは生成されるが sourceMappingURL コメントは付与されません。hidden は、エラー監視サービスにだけ Source Map をアップロードし、一般ユーザーのブラウザには読み込ませたくない場合に有用です。

本番環境でのセキュリティ上の考慮

Source Map を本番環境で公開すると、元のソースコードがそのまま閲覧可能になります。sourcesContent に元コードが含まれている場合はなおさらです。これはセキュリティ上のリスクとなり得るため、いくつかの対策が考えられます。

Source Map を生成するが Web サーバーで .map ファイルへのアクセスを制限する

hidden オプションで sourceMappingURL を付与せず、エラー監視サービスにのみ .map を提供する

エラー監視サービスがスタックトレースを元のソースに変換し、開発者にレポートする

Sentry や Datadog のようなエラー監視サービスは、Source Map をアップロードしておくと、本番環境で発生したエラーのスタックトレースを自動的に元のソースコードの位置に変換してくれます。ユーザーのブラウザには Source Map を配信せず、サーバーサイドで変換を行うため、ソースコードを公開することなくデバッグ情報を得られるという運用が可能になります。

Source Map は開発体験を大きく向上させるツールですが、本番環境での取り扱いには配慮が必要です。開発時はインラインで手軽に、本番では hidden と監視サービスの組み合わせで安全に、という使い分けを意識すると、変換されたコードに対する不安なくデバッグに集中できるようになります。