source map是什么?

460 阅读4分钟

翻译自:What are source maps? (web.dev)

source map 可以提高网页调试体验, 是现代Web开发中至关重要的工具,可以显著改善调试体验。在本文中,我们将探讨源映射的基础知识,它们是如何生成的以及如何改善调试体验。

应用场景都有哪些

前端在发展之初,我们使用纯HTML、CSS和JavaScript构建Web应用程序,然后将相同的文件部署到Web上。 然而,随着我们现在构建的Web应用程序越来越复杂,您的开发工作流可能涉及使用各种工具。例如:

  • 模板语言和HTML预处理器:Pug、Nunjucks、Markdown等。
  • CSS预处理器:SCSS、LESS、PostCSS等。
  • JavaScript框架:Angular、React、Vue、Svelte等。
  • JavaScript元框架:Next.js、Nuxt、Astro等。
  • 高级编程语言:TypeScript、Dart、CoffeeScript等。
  • 等等。列表可以无限延续!

DBPCct3WcUhRuA9qvu1i.webp

这些工具需要进行构建,将开发代码转换为浏览器可以理解的标准HTML、JavaScript和CSS。此外,为了优化性能,通常会对这些文件进行压缩(例如使用Terser来缩小和混淆JavaScript),并将它们组合起来,以减小它们的大小并使它们在网络上更高效。

例如,使用构建工具,我们可以将以下TypeScript文件转译和压缩为单行JavaScript

/* 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)}));

这种压缩后的代码使调试变得更加具有挑战性。将所有内容压缩为单行并缩短变量名称的压缩代码可能会使问题的源头难以确定。这就是souce mpa的作用——它们将编译后的代码映射回原始代码。

生成source map

源代码映射是以.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  
    }  
})

理解source map

source map文件包含有关如何将编译代码映射到原始代码的重要信息,使开发人员能够轻松进行调试。以下是一个源映射文件的示例。

{  
    "mappings": "AAAAA,SAASC,cAAc,WAAWC, ...",  
    "sources": ["src/script.ts"],  
    "sourcesContent": ["document.querySelector('button')..."],  
    "names": ["document","querySelector", ...],  
    "version": 3,  
    "file": "example.min.js.map"  
}

为了理解这些字段,您可以阅读 [规范](Source Map Revision 3 Proposal - Google 文档) 或这篇 [经典文章](Introduction to JavaScript Source Maps - Chrome Developers)。

source map 最重要的方面是映射字段。它使用VLQ基于64位编码字符串将编译后文件中的行和位置映射到相应的原始文件。可以使用source map 可视化工具(例如source-map-visualization)来可视化这种映射。

下面图片展示了我们之前代码示例的可视化效果,通过source-map-visualization生成 luYuSy7CYuB3ZdcCgM6A.webp

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

可视化工具会为原始列中的每一行和相应的生成列中的代码着色。

Mappings 部分显示了代码的解码映射。例如,条目 65-> 2:2 表示:生成的代码:单词 const 在压缩后的内容的 65 位置开始。 原始代码:单词 const 在原始内容的第 2 行第 2 列开始。

这样,开发人员可以快速识别压缩后的代码与原始代码之间的关系,使调试过程更加顺畅。 浏览器开发工具应用这些源映射,帮助您更快地定位调试问题,直接在浏览器中进行调试

下图展示了浏览器开发者工具如何应用源映射,并显示文件之间的映射关系 rfMBvs6g6bZ1Bxblj7cL.webp

source map 扩展

source map 支持扩展,扩展是以x_命名约定开头的自定义字段。一个例子是Chrome DevTools提出的x_google_ignoreList扩展字段。查看x_google_ignoreList了解更多关于这些扩展如何帮助您专注于您的代码的信息。

然而,它并不完美。在我们的示例中,变量greet在构建过程中被优化掉了。它的值直接嵌入到最终的字符串输出中。

2HCLJ3TEHPi3DoNiiTp4.webp

在调试代码时,开发者工具可能无法推断并显示实际值。这不仅是浏览器开发者工具面临的挑战,也会使代码监控和分析变得更加困难。

BH1kRkjkjPAdFmeNqKbr.webp

这当然是可以解决的问题。其中一种方法是在source map中包含作用域信息,就像其他编程语言使用其调试信息一样。

但是,这需要整个生态系统共同努力改进source map规范和实现。关于通过source map提高调试能力的讨论正在积极进行中。

我们期待着改进,使调试工作变得更加轻松。