跳到主要内容

SourceMap 原理

2024年05月08日
柏拉文
越努力,越幸运

一、认识


1.1 bundle.js

通过打包工具, 打包出来的 bundle.js 文件最底部有一行:

// 省略其他代码...
//# sourceMappingURL=index-Cm-P-61P.js.map

这个就是记录原始代码与经过工程化处理代码之间位置映射关系 Map 文件,一切的秘密都在这里。

1.2 bundle.map

我们用简单一点代码来看下.map里的内容结构:

{
"version": 3, // SourceMap 版本
"file": "index-Cm-P-61P.js", // 对应编译产物文件名
"sources": [ // 原始文件路径名,与 sourcesContent 内容一一对应
"../index.js"
],
"sourcesContent": [ // 原始代码内容
"const a = 1;\nconsole.log(a);\n\nconst array = [1, 2, 3, 4, 5];\n\narray.forEach((value, key) => {\n console.log(\"key\", key);\n console.log(\"value\", value);\n});\n\nfunction foo() {\n console.log(\"foo\");\n}\n\nfoo();\n"
],
"names": [ // 原始代码中出现的变量,用于 minify 的场景
"array",
"value",
"key",
"foo"
],
"mappings": "AACA,QAAQ,IAAI,CAAC,EAEb,MAAMA,EAAQ,CAAC,EAAG,EAAG,EAAG,EAAG,CAAC,EAE5BA,EAAM,QAAQ,CAACC,EAAOC,IAAQ,CAC5B,QAAQ,IAAI,MAAOA,CAAG,EACtB,QAAQ,IAAI,QAASD,CAAK,CAC5B,CAAC,EAED,SAASE,GAAM,CACb,QAAQ,IAAI,KAAK,CACnB,CAEAA,EAAK" // 记录打包产物与原始代码的映射关系
}

二、要素


2.1 mappings

mappings 记录打包产物与原始代码的映射关系, 主要有三部分组成:

  1. line: 以;分割出的行映射。每个 mapping 包含由 ; 分割的多行内容。其中每一行都对应生成代码的每行文件的位置映射信息,这里的三行分别对应了编译产物的三行信息

  2. segment: 以,分割出代码中的片段(位置)映射。每一行同包含由 , 分割的多个 segment 信息,其中每个 segment 都对应了产物里每一行里每一个符合所在的列的信息。比如 "AAAA,IAAM,GAAG,GAAG,UAAC,CAAQ,EAAC,CAAQ;IAC5B,OAAO,CAAC,GAAC,CAAC,CAAC;AACb,CAAC,CAAA"

  3. fields: 代码片段, 每个 segment 实际上又包含了几个 field,每个 field 都编码了具体的行列映射信息。每个位置最多由5field 组成,依次为:

    • 第一位: 表示该片段在(转换后的代码的)的第几列。转换后代码所处的列号,如果这是当前行的第一个 segment,那么是个绝对值,否则是相对于上一个 segment 的相对值

    • 第二位: 表示这个片段属于sources属性中的哪一个文件 (sources数组的下标)。表示这个位置属于 sources 属性中的哪一个文件,相对于前一个 segment 的位置(区别于列号,下一行的第一个 segment 仍然是相对于上一行的最后一个 segment,并不会 reset

    • 第三位: 表示这个片段属于转换前代码的第几行。表示这个位置属于转换前代码的第几行,相对位置,同第二列

    • 第四位: 表示这个片段属于转换前代码的第几列。表示这个位置属于转换前代码的第几列,相对位置,同第二列

    • 第五位: 表示这个片段属于 names 属性中的哪一个变量。表示这个位置属于 names 属性中的哪一个变量,相对位置,同第二列

这里 field 片段(位置) 存储映射的值并非是直接的数字值, 而是用到了一种比较高效数值编码算法 —— VLQ(Variable-length Quantity)编码

根据上述这些信息我们实际上就可以实现 SourceMap 的双向映射了,即可以根据 SourceMap 和原始代码的位置信息查找到生成代码的信息,也可以根据 SourceMap 和生成代码的位置信息,查找到原始代码的信息。

三、VLQ 编码


3.1 Sourcemap 为什么会用到 VLQ 编码?

SourceMap 为了节省空间, 所以引入 VLQ 编码, 优化了.map文件的大小,使得.map文件更加紧凑和高效。