JavaScript Source Map

8 篇文章 0 订阅
2 篇文章 1 订阅

问题:线上代码要合并、压缩来减少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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值