JavaScript Source Map
问题:线上代码要合并、压缩来减少http请求数和减小体积,并且压缩后的代码还进行了混淆,那么JavaScript的解释器告诉的:第几行第几列代码出错,这样的报错信息对于合并、压缩、混淆后的代码来说,根本不知道它所对应的原始位置。
解决:Source map,有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码。
什么事Source map?
- 简单说,Source map就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。
- 是一个独立的map文件,与源码在同一个目录下
引用Source map:在转换后的代码尾部,加上如下语句
//# sourceMappingURL=vendor_d0bc9c9d6e6b4ba664cb.js.map
Source map的格式
map文件就是一个一个JavaScript对象,可以被解释器读取。
- version:Source map的版本,目前为3。
- file:转换后的文件名。输出文件
- sourceRoot:转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空。
- sources:转换前的文件。该项是一个数组,表示可能存在多个文件合并。
- names:转换前的所有变量名和属性名。
- mappings:记录位置信息的字符串,下文详细介绍。
如下:
{
version : 3,
file: "out.js",
sourceRoot : "",
sources: ["foo.js", "bar.js"],
names: ["src", "maps", "are", "fun"],
mappings: "AAgBC,SAAQ,CAAEA"
}
{"version":3,"file":"vendor_d0bc9c9d6e6b4ba664cb.js","sources":[],"mappings":";A","sourceRoot":""}
两个文件的各个位置是如何一一对应的?关键就是map文件的mappings属性。
属性mappings
分成三层:
- 第一层是行对应,以分号(;)表示,每个分号对应转换后源码的一行。所以,第一个分号前的内容,就对应源码的第一行,以此类推。
- 第二层是位置对应,以逗号(,)表示,每个逗号对应转换后源码的一个位置。所以,第一个逗号前的内容,就对应该行源码的第一个位置,以此类推。
- 第三层是位置转换,以VLQ编码表示,代表该位置对应的转换前的源码位置。
如:mappings:"AAAAA,BBBBB;CCCCC"=》表示:转换后的源码分成两行,第一行有两个位置,第二行有一个位置。
位置对应的原理:
每个位置使用五位,表示五个字段。从左边算起如下:
- 第一位,表示这个位置在(转换后的代码的)的第几列。
- 第二位,表示这个位置属于sources属性中的哪一个文件。
- 第三位,表示这个位置属于转换前代码的第几行。
- 第四位,表示这个位置属于转换前代码的第几列。
- 第五位,表示这个位置属于names属性中的哪一个变量。
如果某个位置是AAAAA,由于A在VLQ编码中表示0,因此这个位置的五个位实际上都是0。它的意思是,该位置在转换后代码的第0列,对应sources属性中第0个文件,属于转换前代码的第0行第0列,对应names属性中的第0个变量。
注意:
- 所有的值都是以0作为基数的。
- 第五位不是必需的,如果该位置没有对应names属性中的变量,可以省略第五位。
- 每一位都采用VLQ编码表示;由于VLQ编码是变长的,所以每一位可以由多个字符构成。
- 为什么不保存转换后代码的行号,因为输出的文件总是一行,这样输出的行号就可以省略,因为都是0,没必要写出来
- 对于输出后的位置来说,到后边会发现它的列号特别大,为了避免这个问题,采用相对位置进行描述
如上:第一次记录的输入位置和输出位置是绝对的,往后的输入位置和输出位置都是相对上一次的位置移动了多少,例如the的输出位置为(0,-10),因为the在feel的左边数10下才能到这个位置。
举例
假设现在有a.js,内容为feel the force,处理后为b.js,内容为the force feel
以the为例,它在输出中的位置是(0,0),a.js是sources的第1个(这里只是举例),输入中的位置是(0,5),the是names的第2个(这里只是举例)。
那么映射关系为: 0 1 0 5 2
最后将 01052 表示为 Base64 VLQ 即可。
VLQ编码
VLQ是Variable-length quantity 的缩写,是一种通用的、使用任意位数的二进制来表示一个任意大的数字的一种编码方式。VLQ的特点就是可以非常精简地表示很大的数值,用来节省空间。
如何用VLQ编码表示数值。
VLQ编码是变长的。如果(整)数值在-15到+15之间(含两个端点),用一个字符表示;超出这个范围,就需要用多个字符表示。它规定,每个字符使用6个两进制位,正好可以借用Base 64编码的字符表。
在这6个位中,左边的第一位(最高位)表示是否"连续"(continuation)。如果是1,代表这6个位后面的6个位也属于同一个数;如果是0,表示该数值到这6个位结束。
Continuation
| Sign
| |
V V
101011
这6个位中的右边最后一位(最低位)的含义,取决于这6个位是否是某个数值的VLQ编码的第一个字符。如果是的,这个位代表"符号"(sign),0为正,1为负(Source map的符号固定为0);如果不是,这个位没有特殊含义,被算作数值的一部分。
VLQ编码:实例
VLQ编码:需要用最高位表示连续性,如果是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为负;
- 如果不是,这个位没有特殊含义,被算作数值的一部分。
1)如何对数值16进行Base64 VLQ编码。
第一步,将16改写成二进制形式10000。
第二步,在最右边补充符号位。因为16大于0,所以符号位为0,整个数变成100000。
第三步,从右边的最低位开始,将整个数每隔5位,进行分段,即变成1和00000两段。如果最高位所在的段不足5位,则前面补0,因此两段变成00001和00000。
第四步,将两段的顺序倒过来,即00000和00001。
第五步,在每一段的最前面添加一个"连续位",除了最后一段为0,其他都为1,即变成100000和000001。
第六步,将每一段转成Base 64编码。
查表可知,100000为g,000001为B。因此,数值16的VLQ编码为gB。上面的过程,看上去好像很复杂,做起来其实很简单,具体的实现请看官方的base64-vlq.js文件,里面有详细的注释。
2)如何对数值137进行Base64 VLQ编码:
步骤 |
结果 |
将137改写成二进制形式 |
10001001 |
127是正数,末位补0 |
100010010 |
五位一组做分组,不足的补0 |
01000 10010 |
将组倒序排序 |
10010 01000 |
最后一组开头补0,其余补1 |
110010 001000 |
转64进制 |
y和I |
所以 137 通过Base64 VLQ表示为yl
上一篇: 如何把值传给php