source map原理分析

黑夜尽头 | 1622 | 2022-02-17

现在很多打包工具压缩一个js都会生成一个source map。source map可以帮助我们调试线上的代码,因为压缩后的代码往往是一行,利用source map可以将代码还原,那么source map是如何工作的?

位置映射

source map中有一个mapping(映射),它记录了一个文件处理前后的所有内容的位置映射关系,这是它可以还原内容的核心,现在看看咋映射的:

假设现在有a.js,内容为feel the force,处理后为b.js,内容为the force feel,那么mapping应该是多少呢?

上图可以看到,所谓映射,就是指一个字符从一个位置移动到了另一个位置,然后我们将这个位置的变换记录下来。就好比我们在家里打扫卫生,我们要把家具发生移动,同时我们要记住每个家具之前在什么位置,这样等我们打扫完了,就可以还原了。

我们把每个字符的位置移动都写成一种固定的格式,里面包含了之前的位置(输入位置)和移动之后的位置(输出位置),同时还包含输入文件名,为啥要包含输入文件名?因为我们可能把很多文件进行处理输出,如果不写文件名,可能不知道输入位置来自哪个文件。

字符串提取

对于字符来说,例如f,e,e,l四个字符,其实在处理的时候,是将它们作为一个整体移动的,因为处理是不会改变它们内部的顺序,因此我们可以把相关的字符组成组合进行存储:

看看我们现在的存储结构,可以发现有a.js和the这种字符,我们可以把它们抽离出来放在数组里,然后用下标表示它们,这样可以减少mapping的大小:

sources中存储的是所有的输入文件名,names是所有提取的字符组合。需要表示的时候,用下标即可。

省去输出行号

很多时候,我们输出的文件都是一行,这样输出的行号就可以省略,因为都是0,没必要写出来,我们可以把我们的存储单元再缩短一点:

使用相对位置

mapping中的位置记录我们一直用的都是绝对位置,就是这个组合/字符在文件的第几行,第几列,如果文件特别大的话,那么行列就会很大,因此我们可以用相对位置记录行列信息:

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

到现在为止,我们得到了一个简单的mappings:

1. sources:\['a.js'\] 2. names:\['feel','the','force'\] 3. mappings:\[10|0|0|0|0,-10|0|0|5|1,4|0|0|4|2\]

但是我们看看真正的一个source map:

1. "sources":\["test.js"\], 2. "names":\["sayHello","name","console","log"\], 3. "mappings":"AAAA,SAASA,SAASC,MACdC,QAAQC,IAAI,SAAUF"

我们发现很多AABB的,和我们竖线分割不一样啊, 这是咋回事呢?其实这是VLQ编码,专门用来解决竖线分割数字问题的,毕竟竖线看起来又low又浪费空间。

vlq编码

VLQ 是 Variable-length quantity ,是一种可变长度的编码。

我们之前用竖线分割数字,是为了用一个字符串可以存储多个数字,例如:1|23|456|7。但是这样每个|会占用一个字符,vlq的思路则是对连续的数字做上某种标记:

我们可以发现,这种标记只在数字不是结尾的部分才有,如果是123,那么1,2都有标记,最后的3没有标记,没有标记也就意味着完结。

那么这种标记法的具体实现是什么呢?vlq利用6位进行存储,其中第一位表示是否连续的标志,最后一位表示正数/负数。中间只有4位,因此一个单元表示的范围为[-15,15],如果超过了就要用连续标识位了。

我们来看几个用vlq表示的数字就明白了:

上面就是利用vlq编码划分的结果,有一些需要注意的点:

1.如果这个数字在[-15,15]内,一个单元就可以表示,例如上面的7,只需要把7的二进制放入中间的四位就好。

2.如果超过[-15,15],就要用多个单元表示,需要对数字的二进制进行划分,按照..5554的规则划分。把最右边的4位放入第一个单元中,然后每5个放入一个新单元的右边。为啥第一个单元只放4位?因为第一个单元的最后一位是表示正负数的,其他单元的最后一位没必要表示正负了。

3.如果是负数,我们求的是它正数的二进制,放还是按照之前的规则放,只是把第一个单元的最后一位改成1就好。

最后把划分号的6位变成Base64编码,因为Base64也是6位一单元,和这里一样。下面有一个demo,将输入的内容变成字符码数组,然后用vlq&base64编码:

source map原理分析&vlq

上面的demo中有vlq的encode和decode编码实现,想学的朋友可以自行查看。

4位mapping

我们可以自己做一个简单的demo去看看source map生成的mapping。首先安装uglify.js,然后写一个简单的test.js,压缩test.js:

1. npm install uglify\-js \-g 2. uglifyjs test.js \-o output.js \--source\-map "url='output.js.map'"

下面是压缩前后代码和source map

1. /\*test.js\*/ 2. function sayHello(name) { 3.     console.log('hello,', name) 4. } 5. /\*output.js\*/ 6. function sayHello(name){console.log("hello,",name)} 7. //# sourceMappingURL=output.js.map 8. /\*output.js.map\*/ 9. {"version":3,"sources":\["test.js"\],"names":\["sayHello","name","console","log"\],"mappings":"AAAA,SAASA,SAASC,MACdC,QAAQC,IAAI,SAAUF"}

我们用vql解析mappings,得到[0|0|0|0 , 9|0|0|9|0 , 9|0|0|9|1 , 6|0|1|-14|1 , 8|0|0|8|1 , 4|0|0|4 , 9|0|0|10|-2]

首先我们看到有些是5位,有些是4位,5位的我们之前已经知道,输出列|输入文件名|输入行|输入列|字符组合,4位则少了最后的字符组合,一般用来矫正位置。

文章标签: 打包工具前端相关
推荐指数:

转载声明:

原作者: 你大爷还是你大爷

转载自: 《source map原理分析》,如有侵权,请联系本站删除。

2人已点赞

source map原理分析

关于作者 📝

黑夜尽头

大风起兮云飞扬 安得猛士兮走四方 wx: kr_39hd

等级 LV5

粉丝 42

获赞 280

经验 1694