ソースマップは、デバッグを大幅に容易にする、最新のウェブ開発に欠かせないツールです。このページでは、ソースマップの基本、生成方法、デバッグ体験の向上方法について説明します。
ソースマップの必要性
初期のウェブアプリは複雑さが低く構築されていました。デベロッパーは、HTML、CSS、JavaScript ファイルをウェブに直接デプロイしていました。
よりモダンで複雑なウェブアプリでは、開発ワークフローでさまざまなツールが必要になる場合があります。例:
- テンプレート言語と HTML プリプロセッサ: Pug、Nunjucks、Markdown。
- CSS プリプロセッサ: SCSS、LESS、PostCSS。
- JavaScript フレームワーク: Angular、React、Vue、Svelte。
- JavaScript メタ フレームワーク: Next.js、Nuxt、Astro。
- 高水準プログラミング言語: TypeScript、Dart、CoffeeScript。
これらのツールでは、ビルドプロセスを使用して、ブラウザが理解できる標準の HTML、JavaScript、CSS にコードをトランスパイルする必要があります。また、Terser などのツールを使用して、これらのファイルを圧縮して結合し、パフォーマンスを最適化するのも一般的な方法です。
たとえば、ビルドツールを使用すると、次の TypeScript ファイルをトランスパイルして 1 行の JavaScript に圧縮できます。GitHub にあるデモで実際に試してみることができます。
/* A TypeScript demo: example.ts */
document.querySelector('button')?.addEventListener('click', () => {
const num: number = Math.floor(Math.random() * 101);
const greet: string = 'Hello';
(document.querySelector('p') as HTMLParagraphElement).innerText = `${greet}, you are no. ${num}!`;
console.log(num);
});
圧縮されたバージョンは次のようになります。
/* A compressed JavaScript version of the TypeScript demo: example.min.js */
document.querySelector("button")?.addEventListener("click",(()=>{const e=Math.floor(101*Math.random());document.querySelector("p").innerText=`Hello, you are no. ${e}!`,console.log(e)}));
ただし、コードを圧縮すると、デバッグが難しくなる可能性があります。ソースマップを使用すると、この問題を解消できます。コンパイルされたコードを元のコードにマッピングし直すことで、エラーの原因をすばやく見つけることができます。
ソースマップを生成する
ソースマップは、名前が .map
で終わるファイルです(例: example.min.js.map
、styles.css.map
)。Vite、webpack、Rollup、Parcel、esbuild など、ほとんどのビルドツールで生成できます。
一部のツールには、デフォルトでソースマップが含まれています。それらを生成するために追加の構成が必要な場合もあります。
/* Example configuration: vite.config.js */
/* https://vitejs.dev/config/ */
export default defineConfig({
build: {
sourcemap: true, // enable production source maps
},
css: {
devSourcemap: true // enable CSS source maps during development
}
})
ソースマップの概要
デバッグに役立つように、これらのソースマップ ファイルには、コンパイルされたコードが元のコードにマッピングされる方法に関する重要な情報が含まれています。ソースマップの例を次に示します。
{
"mappings": "AAAAA,SAASC,cAAc,WAAWC, ...",
"sources": ["src/script.ts"],
"sourcesContent": ["document.querySelector('button')..."],
"names": ["document","querySelector", ...],
"version": 3,
"file": "example.min.js.map"
}
これらの各フィールドについては、ソースマップの仕様またはソースマップの構造をご覧ください。
ソースマップで最も重要な部分は、mappings
フィールドです。VLQ Base64 エンコード文字列を使用して、コンパイルされたファイル内の行と位置を対応する元のファイルにマッピングします。このマッピングは、source-map-visualization やソースマップの可視化などのソースマップ ビジュアライザを使用して表示できます。
左側の「生成された」列は圧縮されたコンテンツを示し、「元の」(元の)列は元のソースを示しています。
ビジュアライザは、original 列の各行を、generated 列の対応するコードで色分けします。
[mappings] セクションには、コードのデコードされたマッピングが表示されます。たとえば、エントリ 65 -> 2:2
の意味は次のとおりです。
- 生成されたコード: 圧縮されたコンテンツの 65 番目の位置から
const
という単語が始まります。 - 元のコード:
const
という単語は、元のコンテンツの 2 行目 2 列目から始まります。
これにより、デベロッパーは圧縮されたコードと元のコードの関係をすばやく特定でき、デバッグがスムーズになります。
ブラウザのデベロッパー ツールは、これらのソースマップを適用して、ブラウザでデバッグの問題をすばやく特定できるようにします。
ソースマップ拡張機能
ソースマップは、x_
接頭辞で始まるカスタム拡張フィールドをサポートしています。たとえば、Chrome DevTools で提案されている x_google_ignoreList
拡張フィールドがあります。これらの拡張機能がコードに集中しやすくなる仕組みについては、x_google_ignoreList をご覧ください。
ソースマップのデメリット
残念ながら、ソースマッピングは、必要なほど完全であるとは限りません。最初の例では、変数 greet
の値が最終的な文字列出力に直接埋め込まれているにもかかわらず、ビルドプロセス中に最適化されました。
この場合、コードをデバッグするときに、デベロッパー ツールが実際の値を推測して表示できない可能性があります。このようなエラーはコードのモニタリングや 分析を難しくする可能性があります
これは、ソースマップの設計内で解決する必要がある問題です。考えられる解決策の一つは、他のプログラミング言語がデバッグ情報で行っているように、ソースマップにスコープ情報を含めることです。
ただし、そのためには、エコシステム全体が連携してソースマップの仕様と実装を改善する必要があります。ソースマップによるデバッグ可能性の向上に関する最新情報については、GitHub の ソースマップ v4 の提案をご覧ください。