什么是源地图?

源地图是现代 Web 开发中的一个关键工具,它使调试变得更加容易。本页面探讨了源地图的基础知识、其生成方式以及它们如何改善调试体验。

源地图的需求

早期的 Web 应用程序构建复杂度较低。开发人员直接将 HTML、CSS 和 JavaScript 文件部署到 Web。

更现代和复杂的 Web 应用程序可能需要在其开发工作流程中使用各种工具。例如

A brief overview of various tooling.
一些常见的 Web 应用程序开发工具。

这些工具需要一个构建过程,将您的代码转译为浏览器可以理解的标准 HTML、JavaScript 和 CSS。通常的做法是通过使用诸如 Terser 之类的工具来缩小和合并这些文件,从而优化性能。

例如,使用构建工具,我们可以将以下 TypeScript 文件转译和压缩为单行 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.mapstyles.css.map)。它们可以由大多数构建工具生成,包括 VitewebpackRollupParcelesbuild

某些工具默认包含源地图。其他工具可能需要额外的配置才能生成它们

/* Example configuration: vite.config.js */
/* https://vite.vuejs.ac.cn/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 base 64 编码字符串 将编译文件中的行和位置映射到相应的原始文件。您可以使用源地图可视化工具(如 source-map-visualizationSource Map Visualization)查看此映射。

A source map visualization.
可视化工具生成的先前代码示例的可视化。

左侧的生成列显示压缩后的内容,原始列显示原始源代码。

可视化工具使用颜色代码标记原始列中的每一行,以对应于生成列中的代码。

映射”部分显示代码的解码映射。例如,条目 65 -> 2:2 表示

  • 生成的代码:单词 const 在压缩内容中的位置 65 处开始。
  • 原始代码:单词 const 在原始内容中的第 2 行和第 2 列开始。
Mapping entry.
映射可视化,重点关注 65 -> 2:2 条目。

这使开发人员可以快速识别缩小后的代码和原始代码之间的关系,从而使调试过程更加顺畅。

浏览器开发者工具应用这些源地图来帮助您在浏览器中快速查明调试问题。

Developer tools applying a source map.
浏览器开发者工具如何应用源地图并显示文件之间映射的示例。

源地图扩展

源地图支持以 x_ 前缀开头的自定义扩展字段。一个示例是由 Chrome DevTools 提出的 x_google_ignoreList 扩展字段。请参阅 x_google_ignoreList,详细了解这些扩展如何帮助您专注于您的代码。

源地图的缺点

遗憾的是,源映射并不总是像您需要的那样完整。在我们的第一个示例中,变量 greet 在构建过程中被优化掉了,即使其值直接嵌入到最终的字符串输出中。

Variable greet is not mapped.
原始代码中的 greet 变量在映射中丢失了。

在这种情况下,当您调试代码时,开发者工具可能无法推断和显示实际值。这种错误可能会使您的代码监控和分析更加困难。

Variable greet is undefined.
开发者工具找不到 greet 的值。

这是一个需要在源地图设计中解决的问题。一种潜在的解决方案是在源地图中包含作用域信息,就像其他编程语言对其调试信息所做的那样。

但是,这需要整个生态系统协同工作,以改进源地图规范和实现。要关注有关通过源地图改进可调试性的最新进展,请参阅 GitHub 上关于 Source Maps v4 的提案。