SourceMap介绍

1、前言

随着前端代码越开越复杂的状况下,开发者一般会使用webpack、UglifyJS2等工具对代码进行打包变换,这样能够减小代码大小,有效提升访问速度,同时还可以有效的保护源代码不被别人获取。javascript

正常代码hello.js: 
function sayHello() { 
	var name = "Fundebug"; 
	var greeting = "Hello, " + Name; 
	console.log(greeting); 
} 
sayHello(); 

>>>>>>>>>>使用UglifyJS2对源码进行压缩 start>>>>>>>>>>>> 
uglifyjs hello.js \ 
	-m toplevel=true \ 
	-c unused=true,collapse_vars=true \ 
	-o hello.min.js 
>>>>>>>>>>使用UglifyJS2对源码进行压缩 end>>>>>>>>>>>> 

压缩后的代码hello.min.js: 
function o(){var o="Hello, "+Name;console.log(o)}o(); 

复制代码

然而压缩代码的报错信息是很难Debug的,由于它的行号和列号已经失真。
执行hello.js的报错信息,咱们可以直接定位到出错的位置。 html

而执行压缩后的hello.min.js的报错信息,
前端

对比压缩先后的出错信息,咱们会发现,错误行号和列号已经失真,且函数名也通过了变换。而对于真实的前端项目,开发者会将数十个源文件压缩为一个文件,这时,错误的列号可能多达数千,且出错的真实文件名也是很难肯定的,这样的话,压缩代码的报错信息是很难Debug的。
而Source Map则能够用于还原真实的出错位置,帮助开发者更快的Debug。java

2、什么是SourceMap

简单说,Source map就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每个位置,所对应的转换前的位置。
有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码。这无疑给开发者带来了很大方便。webpack

一、生成SourceMap文件

目前各类主流前端任务管理工具,打包工具都支持生成Source Map,具体能够查看生成SourceMap
使用UglifyJS2时指定source-map选项便可生成Source Map:web

>>>>>>>>>>使用UglifyJS2生成SourceMap start>>>>>>>>>>>> 
uglifyjs hello.js \ 
	-m toplevel=true \ 
	-c unused=true,collapse_vars=true \ 
	--source-map url='hello.min.js.map' \ 
	-o hello.min.js 
>>>>>>>>>>使用UglifyJS2生成SourceMap end>>>>>>>>>>>>

添加了SourceMap的hello.min.js:
function o(){var o="Hello, "+Name;console.log(o)}o();
//# sourceMappingURL=hello.min.js.map

生成的SourceMap文件hello.min.js.map:
{
 "version": 3,
 "sources": [
 "hello.js"
 ],
 "names": [
 "sayHello",
 "greeting",
 "Name",
 "console",
 "log"
 ],
 "mappings": "AAAA,SAASA,IACL,IACIC,EAAW,UAAYC,KAC3BC,QAAQC,IAAIH,GAEhBD"
}
复制代码

二、SourceMap文件解析

由hello.min.js.map可知,Source Map是一个JSON文件,而它包含了代码转换先后的位置信息。也就是说,给定一个转换以后的压缩代码的位置,就能够经过Source Map获取转换以前的代码位置,反过来也同样。Source Map各个属性的含义以下:数组

  • version:Source Map的版本号。
  • sources:转换前的文件列表。该项是一个数组,表示可能存在多个文件合并。
  • names:转换前的全部变量名和属性名。
  • mappings:记录位置信息的字符串,通过编码。
  • file:(可选)转换后的文件名。
  • sourceRoot:(可选)转换前的文件所在的目录。若是与转换前的文件在同一目录,该项为空。
  • sourcesContent:(可选)转换前的文件内容列表,与sources列表依次对应。

Source Map真正神奇之处在于mappings属性,它记录了位置是如何对应的。bash

三、mappings属性

sourcemap的mappings属性的值是将位置映射关系采用Base 64 VLQ编码后生成的字符串。
mappings属性值包含的信息能够按照下面的方式进行拆分:app

  1. 第一层是行对应,以分号(;)标识编译后代码的每一行,便是分号间隔的内容表明编译后代码的一行;
  2. 第二层是位置对应,以逗号(,)标识编译后代码该行中的每个映射位置,便是逗号间隔的内容表明一个映射位置;
  3. 第三层是位置转换,以5组VLQ编码字段标识源码和编译后代码的具体映射信息。从左至右每组表示以下:
    • 第1组,表示对应编译后代码的第几列(从第0列开始计算);
    • 第2组,表示源码所属文件在sources数组中的索引值;
    • 第3组,表示对应源码的第几行(从第0行开始计算);
    • 第4组,表示对应源码的第几列(从第0列开始计算);
    • 第5组,表示在names数组中的索引值,若没有则可省略。

注意:每组VLQ编码字段有0~N个VLQ编码字符组成,如 qC | A | A | U | H。
例如“AAAAA”表示的就是:该位置在转换后代码的第0列,对应sources属性中第0个文件,属于转换前代码的第0行第0列,对应names属性中的第0个变量。函数

四、VLQ编码

VLQ编码最先用于MIDI文件,后来被多种格式采用。它的特色就是能够很是精简地表示很大的数值。它规定,每一个字符使用6个两进制位。
以下图所示:

  1. 在这6个位中,左边的第一位(最高位)表示是否"连续"(continuation)。若是是1,表明这6个位后面的6个位也属于同一个数;若是是0,表示该数值到这6个位结束。
  2. 这6个位中的右边最后一位(最低位)的含义,取决于这6个位是不是某个数值的VLQ编码的第一个字符。若是是的,这个位表明"符号"(sign),0为正,1为负(Source map的符号固定为0);若是不是,这个位没有特殊含义,被算做数值的一部分。

上面看的可能比较迷糊,咱们看一个例子,如何对数值137进行VLQ编码:

  • 先把 137 转换成二进制:10001001
  • 因为 137 是正数,因此在最低位补0 变成 100010010
  • 按照 5bit 一组的方式分组,变成 01000 10010
  • 按照从低位到高位的顺序,以 5bit 一组为单位,依次拿出数据作转换。
  • 先拿出第一组 10010 , 由于后面还有数据,因此须要在高位补 1,变成 110010 ,编码成 Base64: y
  • 再拿出第二组,也是咱们这里的最后一组,01000 , 因为是最后一组,因此在高位补 0 变成 001000,编码成 Base64: I
  • 按照从低位到高位的顺序把这些 ASCII 字符拼接起来,变成 yI

注意:转换为二进制码以后,是从低位到高位的进行Base64转换。

下图为base64的映射表:


根据以上对VLQ编码的方式,咱们可以掌握了如何将一个数值进行base64 VLQ编码,了解了编码以后,解码也没必要在讲解了,倒推的回去就好了。这里推荐一个在线的VLQ编解码网站: Base64VLQ在线编解码网站

五、mappings位置数值解读

咱们了解完了VLQ的编解码以后,咱们将开头生成的SourceMap文件的mappings属性分析一下看看是否可以一一对应。
开头生成的SourceMap文件的mappings属性为:

"mappings": "AAAA,SAASA,IACL,IACIC,EAAW,UAAYC,KAC3BC,QAAQC,IAAIH,GAEhBD"
复制代码

咱们使用Base64VLQ在线编解码网站把内容解码一下:

[0,0,0,0], [9,0,0,9,0], [4,0,1,-5], [4,0,1,4,1], [2,0,0,11],
[10,0,0,12,1], [5,0,1,-27,1], [8,0,0,8,1], [4,0,0,4,-3], [3,0,2,-16,-1]
复制代码

注意:mappings的位置值都是相对位置,每一个值都是相对于前一个位置值的。(当文件内容巨大时,精简后的编码也有可能会由于数字位数的增长而变得很长,同时,处理较大数字老是不如处理较小数字容易和方便。因而mappings中的位置记录是记录的这些位置的相对值。)
所以每个位置值加上前一个位置值能够获得:

[0,0,0,0], [9,0,0,9,0], [13,0,1,4], [17,0,2,8,1], [19,0,2,19],
[29,0,2,31,2], [34,0,3,4,3], [42,0,3,12,4], [46,0,3,16,1], [49,0,5,0,0]
复制代码

整理一下能够得出以下结果:

(a,b)=>name(m,n)表示生成代码中的a行b列对应原始代码中的m行n列的位置,而且在原始代码中这个位置的变量名是name。

(0,0)=>(0,0)
(0,9)=>sayHello(0,9)
(0,13)=>(1,4)
(0,17)=>greeting(2,8)
(0,19)=>(2,19)
(0,29)=>Name(2,31)
(0,34)=>console(3,4)
(0,42)=>log(3,12)
(0,46)=>greeting(3,16)
(0,49)=>(5,0)
复制代码

参考:

SourceMap详解--阮一峰
SourceMap详解--网易云
Base64 VLQ编码规则

相关文章
相关标签/搜索