SourceMap 映射原理解析

在浏览器中开启Source Map

  • Source Map在浏览器中默认是关闭的,这样就不会影响正常用户。当我们开启后,浏览器就根据压缩代码中指定的Source Map地址去请求 map 资源。
    在这里插入图片描述

Source Map基本结构

{
    "version":3, //source map的版本
    "sources":["index.js"], //转换前的文件,他是一个数组,因为压缩后的文件可能由几个不同的文件组合起来的
    "names":["add","a","b"], //转换前所有的变量名和属性名
    "mappings":"AAAA,SAASA,IAAIC,EAAGC,GACd,OAAOD,EAAIC" // 用来记录映射信息的base64编码
}

Source Map功能

  • 输入(js) ⇒ 处理转换(压缩) ⇒ 输出(js)
  • Source Map的功能是帮助我们在拿到输出后还原回输入

Source Map映射解析

  • 最直观的想法恐怕是,将生成的文件中每个字符位置对应的原位置保存起来,一一映射
“feel the force” ⇒ Yoda_input.txt(压缩输出) ⇒ “the force feel”

  • 将上面的的输入与输出列成表格可以得出这个转换后输入与输出的对应关系。
输出位置输入在原输入中的位置字符
行 1, 列 0Yoda_input.txt行 1, 列 5t
行 1, 列 1Yoda_input.txt行 1, 列 6h
行 1, 列 2Yoda_input.txt行 1, 列 7e
行 1, 列 4Yoda_input.txt行 1, 列 9f
行 1, 列 5Yoda_input.txt行 1, 列 10o
行 1, 列 6Yoda_input.txt行 1, 列 11r
行 1, 列 7Yoda_input.txt行 1, 列 12c
行 1, 列 8Yoda_input.txt行 1, 列 13e
行 1, 列 10Yoda_input.txt行 1, 列 0f
行 1, 列 11Yoda_input.txt行 1, 列 1e
行 1, 列 12Yoda_input.txt行 1, 列 2e
行 1, 列 13Yoda_input.txt行 1, 列 3l
  • 上面可以直观看出,生成文件中 (1,0) 位置的字符对应源文件中 (1,5)位置的字符,…
    将上面的表格整理记录成一个映射编码看起来会是这样的
mappings(283 字符):1|0|Yoda_input.txt|1|5, 1|1|Yoda_input.txt|1|6, 1|2|Yoda_input.txt|1|7, 1|4|Yoda_input.txt|1|9, 1|5|Yoda_input.txt|1|10, 1|6|Yoda_input.txt|1|11, 1|7|Yoda_input.txt|1|12, 1|8|Yoda_input.txt|1|13, 1|10|Yoda_input.txt|1|0, 1|11|Yoda_input.txt|1|1, 1|12|Yoda_input.txt|1|2, 1|13|Yoda_input.txt|1|3

省去输出文件中的行号

  • 大多数情况下处理后的文件行数都会少于源文件,特别是 js,使用 UglifyJS 压缩后的文件通常只有一行。因此,没必要在每条映射中都带上输出文件的行号,转而在这些映射中插入;来标识换行,可以节省大量空间。
mappings (245 字符): 0|Yoda_input.txt|1|5, 1|Yoda_input.txt|1|6, 2|Yoda_input.txt|1|7, 4|Yoda_input.txt|1|9, 5|Yoda_input.txt|1|10, 6|Yoda_input.txt|1|11, 7|Yoda_input.txt|1|12, 8|Yoda_input.txt|1|13, 10|Yoda_input.txt|1|0, 11|Yoda_input.txt|1|1, 12|Yoda_input.txt|1|2, 13|Yoda_input.txt|1|3;

换元法

  • 一共有三个单词,拿输出文件中 the 来说,当我们通过它的第一个字母t(1,0)确定出对应源文件中的位置(1,5),后面的he 其实不用再记录映射了,因为the 可以作为一个整体来看,试想 js 源码中一个变量名,函数名这些都不会被拆开的,所以当我们确定的这个单词首字母的映射关系,那整个单词其实就能还原到原来的位置了
序号符号
0the
1force
2feel
  • 于是得到一个所有包含所有符号的数组:
names: ['the','force','feel']
  • 在记录时,只需要记录一个索引,还原时通过索引来这个names 数组中找即可。所以上面映射规则中最后一列本来记录了每个字符,现在改为记录一个单词,而单词我们只记录其在抽取出来的符号数组中的索引。
  • the 的映射可以简化为:
0|Yoda_input.txt|1|5, 1|Yoda_input.txt|1|6, 2|Yoda_input.txt|1|7
//简化为:
0|Yoda_input.txt|1|5|0

  • 同时,考虑到代码经常会有合并打包的情况,即输入文件不止一个,所以可以将输入文件抽取一个数组,记录时,只需要记录一个索引,还原的时候再到这个数组中通过索引取出文件的位置及文件名即可
sources: ['Yoda_input.txt']
  • 所以上面the 的映射进一步简化为:
0|0|1|5|0
  • 最终获得的映射结构为
sources: ['Yoda_input.txt']
names: ['the','force','feel']
mappings (31 字符): 0|0|1|5|0, 4|0|1|9|1, 10|0|1|0|2;

在这里插入图片描述

记录相对位置

  • 当文件内容巨大时,上面精简后的编码也有可能会因为数字位数的增加而变得很长
  • 将上面记录的这些位置用相对值来记录
原输出位置相对输出位置
行 1, 列 0行 1, 列 0
行 1, 列 4行 1, 列 4(上一值 + 4 = 4)
行 1, 列 10行 1, 列 6(上一值 + 6 = 10)
  • 将输出后的列,原代码里的行列都用相对位置表示后
sources: ['Yoda_input.txt']
names: ['the','force','feel']
mappings (31 字符): 0|0|1|5|0, 4|0|1|4|1, 6|0|1|-9|1;

VLQ (Variable Length Quantities)

VLQ 以二进制方式的方式呈现

  • 用6个位来记录一个数字(可表示至多64个值),用其中一个位来标识它是否未结束(下方 C 标识),再用一位标识正负(下方 S),剩下还有四位用来表示数值。用这样6个字节组成的一组拼起来就可以表示出我们需要的数字串了。
  • C为1表示未结束,S为1表示负数,其他Value存储值,第一个6位的组,只能存储4个value
  • 但如果未结束,因为第一个组中已经标明了该数字的正负,所以后续的字节组中无需再标识,于是可以多出一位来作表示值
B5B4B3B2B1B0
CValueValueValueValueS
//最后一位的1表示未结束,第一位的1表示负数
位      结果   
000000	0
000001 	-0
000010	1
000011	-1
000100	2
000101	-2
…	…
011110	15
011111	-15
100000	未结束的0
100001	未结束的-0
100010	未结束的1
100011	未结束的-1
…	…
111110	未结束的15
111111	未结束的-15
  • 举例

数值	二进制
1	1
23	10111
456	111001000
7	111
  • 对1进行编码
B5(C) B4	B3	B2	B1	B0(S)
0	  0		0	0	1	0
  • 对23进行编码
    • 23的二进制为10111一共需要5位,第一组字节组只能提供4位来记录值,所以用一组字节组不行,需要使用两组字节组。将 10111拆分为两组,后四位0111放入第一个字节组中,剩下一位1放入第二个字节组中
    • 因为第一个组已经表示了正负S,所以第二个组可以有五位来存储value

B5(C)	B4	B3	B2	B1	B0(S)		B5(C)	B4	B3	B2	B1	B0
1		0	1	1	1	0			0		0	0	0	0	1
  • 对456进行编码
    • 456的二进制111001000需要占用9个字节,同样,一个字节组放不下,先拆出最后四位(1000)放入一个首位字节组中,剩下的5位(11100)放入跟随的字节组中。
B5(C)	B4	B3	B2	B1	B0(S)		B5(C)	B4	B3	B2	B1	B0
1		1	0	0	0	0			0		1	1	1	0	0
  • 对7进行编码

B5(C)	B4	B3	B2	B1	B0(S)
0		0	1	1	1	0
  • 将上面的编码合并得到最终的编码:
000010 101110 000001 110000 011100 001110

在这里插入图片描述

  • 根据base64编码表,得到最终的字符串:
CuBwcO

利用 Base64 VLQ 编码生成最终的 srouce map

  • 前面我们已经得到的编码为
sources: ['Yoda_input.txt']
names: ['the','force','feel']
mappings (31 字符): 0|0|1|5|0, 4|0|1|4|1, 6|0|1|-9|1;

  • 现在来编码 0|0|1|5|0。先用二进制对每个数字进行表示,再转成 VLQ 表示:
0-> 0 -> 000000 //0
0-> 0 -> 000000 //0
1-> 1 -> 000010 //2
5-> 101 -> 001010 // 10
0-> 0 -> 000000 //0

//合并后的编码为:
000000 000000 000001 000101 000000

//再转 Base64 后得到字符形式的结果:
AACKA
  • 最终得到的Source Map看起来是这样的:
sources: ['Yoda_input.txt']
names: ['the','force','feel']
mappings (18 字符): AACKA, IACIC, MACTC;

//实际的Source Map结构
{
    "version": 3,
    "file": "Yoda_output.txt",
    "sources": ["Yoda_input.txt"],
    "names": ["the", "force", "feel"],
    "mappings": "AACKA,IACIC,MACTC;"
}

略去不必要的字段

真实场景中,有些情况下某些字段其实不必要,这时就可以将其省略

  • 5 - 包含全部五个部分:输出文件中的列号,输入文件索引,输入文件中的行号,输入文件中的列号,符号索引
  • 4 - 输出文件中的列号,输入文件索引,输入文件中的行号,输入文件中的列号
  • 1 - 输出文件中的列号
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值