Source Map 的工作原理

了解 Source Map 如何建立源代码与压缩代码的映射关系

问题

Source Map 是如何将压缩后的代码映射回原始源代码的?它的工作原理是什么?

解答

Source Map 不影响页面性能

Source Map 文件只有在打开开发者工具时才会下载,普通用户不会触发下载。虽然在 Network 面板中看不到,但使用抓包工具可以发现,打开 dev tools 时才开始下载 source map 文件。

Source Map 的标准格式

Source Map 遵循 Google 和 Mozilla 制定的标准(版本 3),各打包工具基本都基于 source-map 库生成。标准格式如下:

{
  "version": 3,
  "file": "min.js",
  "names": ["bar", "baz", "n"],
  "sources": ["one.js", "two.js"],
  "sourceRoot": "http://example.com/www/js/",
  "mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA"
}

字段说明:

  • version: 标准版本号,当前为 3
  • file: 编译后的文件名
  • names: 变量名数组,用于 mappings 中引用
  • sources: 源文件名数组
  • mappings: 核心字段,记录源码与编译后代码的位置映射关系

浏览器如何关联 Source Map

以 webpack 打包为例,源代码:

// index.js
const a = 1
console.log(a);

打包后的 bundle.js 文件末尾会添加注释:

console.log(1);
//# sourceMappingURL=bundle.js.map

除了注释方式,还可以通过 HTTP 响应头 SourceMap: <url> 来指定。

mappings 字段的编码原理

这是 Source Map 最核心的部分。以打包后的 source map 为例:

{
  "sources": ["webpack://webpack-source-demo/./src/index.js"],
  "names": ["console", "log"],
  "mappings": "AACAA,QAAQC,IADE"
}

mappings 的组成:

  • 英文字母:Base64 VLQ 编码,表示位置信息
  • 逗号:分隔同一行代码中的不同部分
  • 分号:表示换行

Base64 VLQ 编码:

为了减小文件体积,使用 Base64 编码表示数字。例如 A 代表 0,C 代表 2。

每组英文字母的含义(5 个位置信息):

  1. 压缩代码的第几列
  2. 对应 sources 数组中的第几个源文件
  3. 源代码第几行
  4. 源代码第几列
  5. 对应 names 数组中的第几个变量名

AACAA 为例,解码为 [0,0,1,0,0],表示:

  1. 压缩代码第 1 列
  2. 第 1 个源文件(index.js)
  3. 源代码第 2 行
  4. 源代码第 1 列
  5. names 数组第 1 个索引(console)

注意:除了每个分号后的第一组数字表示绝对位置外,其他都是相对于前一个位置的偏移量。

Source Map 的生成

Source Map 由 AST(抽象语法树)生成。AST 中包含了代码的位置信息(行号、列号),这些信息被用来生成 mappings 字段。

实际应用场景

监控系统中的典型应用流程:

  1. 构建时通过插件上传源代码和 source map 到监控平台
  2. 客户端上报错误信息(包含压缩代码的位置)
  3. 平台使用 source-map 库解析,还原出源代码的真实报错位置
  4. 开发者快速定位并修复线上问题

关键点

  • Source Map 只在打开开发者工具时下载,不影响普通用户的页面性能
  • mappings 使用 Base64 VLQ 编码压缩位置信息,每组包含 5 个位置数据:压缩代码列号、源文件索引、源代码行号、源代码列号、变量名索引
  • 浏览器通过 //# sourceMappingURL 注释或 HTTP 响应头找到 source map 文件
  • Source Map 由 AST 生成,利用语法树中的位置信息建立映射关系
  • 主要应用于监控系统,帮助还原线上错误的源码位置