sourceMap是个啥

10次阅读

共计 2391 个字符,预计需要花费 6 分钟才能阅读完成。

为啥用 sourceMap

这几天在搞前端错误日志,做过线上发布的都知道,我们发布到生产环境的代码,一般都有如下步骤:

  • 压缩混淆,减小体积
  • 多个文件合并,减少 HTTP 请求数
  • 通过编译或者转译,将其他语言编译成JavaScript

这三个步骤,都使得实际运行的代码不同于开发代码,不管是 debug 还是捕获线上的报错,都会变得困难重重。

解决这个问题的方法,就是使用sourceMap

啥是 sourceMap

简单说,sourceMap就是一个文件,里面储存着位置信息。

仔细点说,这个文件里保存的,是转换后代码的位置,和对应的转换前的位置。

有了它,出错的时候,通过断点工具可以直接显示原始代码,而不是转换后的代码。

sourceMap 长啥样

通过 webpack 等工具,我们可以使用 sourceMap,这里不细说配置方法,可以看这里

sourceMap是一个 map 文件,与源码在同一个目录下。

在压缩代码的最后一行,会有这样的一个引用:

//# sourceMappingURL=app.js.map

指向的就是我们的 map 文件。

sourceMap的格式如下:

{
    version : 3, //SourceMap 的版本,目前为 3
    sources: ["foo.js", "bar.js"], // 转换前的文件,该项是一个数组,表示可能存在多个文件合并
    names: ["src", "maps", "are", "fun"], // 转换前的所有变量名和属性名
    mappings: "AACvB,gBAAgB,EAAE;AAClB;", // 记录位置信息的字符串
    file: "out.js", // 转换后的文件名
    sourcesContent: "\t// The module cache\n", // 转换后的代码
    sourceRoot : "" // 转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空
}

其他的都很好解释,我们详细说一下 mappings 属性。

mappings

"AACvB,gBAAgB,EAAE;AAClB;" 为例:

  • 每个分号对应转换后源码的一行;
  • 每个逗号对应转换后源码的一个位置;
  • AACvB代表该位置转换前的源码位置,以 VLQ 编码表示;

位置对应的原理

位置关系的保存经历了诸多步骤和优化,这个不详细说了,想看的可以看这里,我们只说最后的结果。

在每个位置中:

  • 第一位,表示这个位置在【转换后代码】的第几列。
  • 第二位,表示这个位置属于【sources 属性】中的哪一个文件。
  • 第三位,表示这个位置属于【转换前代码】的第几行。
  • 第四位,表示这个位置属于【转换前代码】的第几列。
  • 第五位,表示这个位置属于【names 属性】的哪一个变量。

举例

假设现在有a.js,内容为feel the force,处理后为b.js,内容为the force feel

the 为例,它在输出中的位置是 (0,0),a.jssources的第 1 个(这里只是举例),输入中的位置是 (0,5),thenames的第 2 个(这里只是举例)。

那么映射关系为:
0 1 0 5 2

最后将 01052 表示为 Base64 VLQ 即可。

说明:

  • 所有的值都是以 0 作为基数
  • 第五位不是必需的,如果该位置没有对应 names 属性中的变量,可以省略第五位
  • 每一位都采用 VLQ 编码表示,由于 VLQ 编码是可变长的,所以每一位可以由多个字符构成
  • 为什么不保存转换后代码的行号,因为我们输出的文件总是一行,这样输出的行号就可以省略,因为都是 0,没必要写出来
  • 对于输出后的位置来说,到后边会发现它的列号特别大,为了避免这个问题,采用相对位置进行描述

相对位置是啥呢,看示意图:

第一次记录的输入位置和输出位置是绝对的,往后的输入位置和输出位置都是相对上一次的位置移动了多少,例如 the 的输出位置为 (0,-10), 因为thefeel的左边数 10 下才能到这个位置。

VLQ 编码

VLQVariable-length quantity 的缩写,是一种通用的、使用 任意位数的二进制 来表示一个 任意大的数字 的一种编码方式。这种编码最早用于 MIDI 文件,后来被多种格式采用,它的特点就是可以非常精简地表示很大的数值,用来节省空间。

这种编码需要用最高位表示连续性,如果是 1,代表这组字节后面的一组字节也属于同一个数;如果是 0,表示该数值到这就结束了。

这样干巴巴说不太容易懂,还是举个栗子说明一下吧。

如何对数值 137 进行 VLQ 编码:

步骤 结果
将 137 改写成二进制形式 10001001
七位一组做分组,不足的补 0 0000001 0001001
最后一组开头补 0,其余补 1 10000001 00001001

所以,137 的 VLQ 编码形式为10000001 00001001

Base64 VLQ

与一般的 VLQ 的区别:

  • 一个 Base64 字符只能表示 6bit(2^6) 的数据
  • Base64 VLQ需要能够表示负数,于是用最后一位来作为符号标志位。
  • 由于只能用 6 位进行存储,而第一位表示是否连续的标志,最后一位表示正数 / 负数。中间只有 4 位,因此一个单元表示的范围为[-15,15],如果超过了就要用连续标识位了。

表示正负的方式:

  • 如果这组数是某个数值的 VLQ 编码的第一组字节,那它的最后一位代表 ” 符号 ”,0 为正,1 为负;
  • 如果不是,这个位没有特殊含义,被算作数值的一部分。

我们再来举个栗子说明下使用方法。

如何对数值 137 进行 Base64 VLQ 编码:

步骤 结果
将 137 改写成二进制形式 10001001
127 是正数,末位补 0 100010010
五位一组做分组,不足的补 0 01000 10010
将组倒序排序 10010 01000
最后一组开头补 0,其余补 1 110010 001000
转 64 进制 y 和 I

所以 137 通过 Base64 VLQ 表示为yl

可以看出:

  • Base64 VLQ 中,编码顺序是从 低位到高位
  • 而在 VLQ 中,编码顺序是从 高位到低位

参考文章

  • JavaScript Source Map 详解
  • BASE64 VLQ 编码规则
  • source map 原理分析 &vlq
正文完
 0