随着前端代码越开越复杂的状况下,开发者一般会使用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
简单说,Source map就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每个位置,所对应的转换前的位置。
有了它,出错的时候,除错工具将直接显示原始代码,而不是转换后的代码。这无疑给开发者带来了很大方便。webpack
目前各类主流前端任务管理工具,打包工具都支持生成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"
}
复制代码
由hello.min.js.map可知,Source Map是一个JSON文件,而它包含了代码转换先后的位置信息。也就是说,给定一个转换以后的压缩代码的位置,就能够经过Source Map获取转换以前的代码位置,反过来也同样。Source Map各个属性的含义以下:数组
Source Map真正神奇之处在于mappings属性,它记录了位置是如何对应的。bash
sourcemap的mappings属性的值是将位置映射关系采用Base 64 VLQ编码后生成的字符串。
mappings属性值包含的信息能够按照下面的方式进行拆分:app
注意:每组VLQ编码字段有0~N个VLQ编码字符组成,如 qC | A | A | U | H。
例如“AAAAA”表示的就是:该位置在转换后代码的第0列,对应sources属性中第0个文件,属于转换前代码的第0行第0列,对应names属性中的第0个变量。函数
VLQ编码最先用于MIDI文件,后来被多种格式采用。它的特色就是能够很是精简地表示很大的数值。它规定,每一个字符使用6个两进制位。
以下图所示:
上面看的可能比较迷糊,咱们看一个例子,如何对数值137进行VLQ编码:
注意:转换为二进制码以后,是从低位到高位的进行Base64转换。
下图为base64的映射表:
咱们了解完了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)
复制代码