想必你们都对这一段截图耳熟能详: html
由于检测并加载了sourcemap,因此能够直接定位到编译前代码的特定位置。因此用一句话解释sourcemap很简单,就是一段维护了先后代码映射关系的json描述文件。但若是细究其内容,能够泡杯茶说一段故事了~html5
在2009年google的一篇 文章 中,在介绍Cloure Compiler(一款js压缩优化工具,可类比于uglify-js)时,google也顺便推出了一款调试工具:firefox插件Closure Inspector,以方便调试编译后代码。这就是sourcemap的最初代啦!webpack
You can use the compiler with Closure Inspector , a Firebug extension that makes debugging the obfuscated code almost as easy as debugging the human-readable source.git
2010年,在第二代即 Closure Compiler Source Map 2.0 中,sourcemap肯定了统一的json格式及其他规范,已几乎具备如今的雏形。最大的差别在于mapping算法,也是sourcemap的关键所在。第二代中的mapping已决定使用base 64编码,可是算法同如今有出入,因此生成的.map相比如今要大不少。es6
2011年,第三代即 Source Map Revision 3.0 出炉了,这也是咱们如今使用的sourcemap版本。从文档的命名看来,此时的sourcemap已脱离Clousre Compiler,演变成了一款独立工具,也获得了浏览器的支持。这一版相较于二代最大的改变是mapping算法的压缩换代,使用VLQ编码生成base64前的mapping,大大缩小了.map文件的体积。github
Sourcemap发展史的有趣之处在于,它做为一款辅助工具被开发出来。最后它辅助的对象日渐式微,而它却成为了技术主体,被写进了浏览器中。web
{
"version": 3,
"sources": ["test.es6.js"],
"names": [],
"mappings": ";;AAAA,IAAM,MAAM,GAAG,SAAT,MAAM,CAAI,CAAC;SAAK,CAAC,GAAG,CAAC;CAAA,CAAC",
"file": "test.js",
"sourcesContent": ["const square = (x) => x * x;"]
}
复制代码
这就是一段简单的map文件,其中算法
接下来,就来详细瞅瞅主要元素mapping把!json
据 html5rocks的sourcemap教程 (这也是最经典的一篇sourcemap博文了)所说,数组
sourcemap v1最开始生成的sourcemap文件大概有转换后文件的10倍大。sourcemap v2将之减小了50%,v3又在v2的基础上减小了50%。因此目前133k的文件对应的sourcemap文件大小大概在300k左右。
那么它是如何用最精简的方式维护现文件到源文件的映射的呢?对于整个映射而言,重要的几要素无非是“原文件、原行数、原列数、原变量名”,因此对映射算法的优化,实质也就是对这一段数据的存储优化。
我发现了一篇很是好的文章How do source maps work,做者经过简单的示例解释了map的大体工做原理。接下来我会介(梗)绍(概)下做者的几个要点。
原代码:
const square = (x) => x * x;
复制代码
Babel编译后代码:
"use strict";
var square = function square(x) {
return x * x;
};
//# sourceMappingURL=test.js.map
复制代码
Mappings(接下来的分析都会以这份mapping为准喔):
";;AAAA,IAAM,MAAM,GAAG,SAAT,MAAM,CAAI,CAAC;SAAK,CAAC,GAAG,CAAC;CAAA,CAAC"
复制代码
如上,能够看到这段mappings有几个关键元素:分号;
逗号,
及分隔出的一串一眼看不懂的字母。其中:
附:【base64 VLQ编码 - 十进制转换表 】(先仅供参考,具体转换规则见下文VLQ编码部分)
一段segment中的VLQ编码,其实既能够是4位,也能够是5位。每一位都有特定含义:
带着解释,再看如下两个问题
上述mapping以;;开头,是由于babel编译后新增的use strict及下面空行没有队对应的原文~
[AAAA,IAAM] 转换为VLQ编码是[0000, 4006]。第二个segment里的4表明square在转换后文件的第4列,而6表明square在现文件的第6列。
0|1|2|3|4|
v|a|r| |s|q|u|a|r|e|
0|1|2|3|4|5|6|
c|o|n|s|t| |s|q|u|a|r|e|
复制代码
以上,相信再看见一段mappings时,不说能立马知道它表明了哪一段源文件,但至少不会两眼一抹黑了~
其实从以上过程读来,在知道了规则的状况下,反编译一段编码老是容易的。难的实际上是创造和优化规则的过程。好比将源文件名放在一个数组中,经过存储原文件在数组中的index,而不是源文件name自己;好比存储的column index是相对位置而不是绝对位置;好比经过;分隔行经过,分隔列,而不是记录行号和列号;好比为何选择了经base64转换的VLQ编码…正是这些微小的一点点比对出来的细节,才能积少成多,将mapping文件最大化的压缩。
要了解VLQ编码,原本我是拒绝的…我也许永远也不会用到…可是一想来都来了,不如也顺便了解一下叭!就当作陶冶情操呢~
So..了解完成后,感受文本编码和网络技术一般会讲到传递xx包要怎么封装一串数据同样,重点都是差很少的。一是如何分割数字(即表示连续),好比1和17在一块儿组成117的时候,怎么来表示这是1和17而不是一百一十七。二是如何表示正负。
一个VLQ小节有6位(因此恰好适合经过base64转换成字符呀),第一位表明连续,最后一位通常表明正负,因此中间至少有4位能够表示value。
如上,能够看到-15~15均可以用一个VLQ小节来表示。当|Number|>15时,则须要用到多个VLQ小节。另外,正负只需用第一小节的最后一位表示。因此多个小节时,第>1个小节的第6位能够不表明正负,也表明内容。
若是本身有须要使用VLQ编码的话,则能够参考市面上现成的库~好比GitHub - Rich-Harris/vlq
用webpack的人都知道,它有好多种sourcemap选项Devtool | webpack。在没了解sourcemap的深层本质以前,我对这几个选项都只知其一;不知其二,就死记硬背着cheap-module-eval-source-map是最优解╮( ̄▽ ̄")╭
其实这几个选项,都是关键字cheap, module, eval, inline, hidden + sourcemap的排列组合,只要清楚各自的做用及意义就行了。
对我而言,行数必需列数没必要需,映射loader编译前的代码也必需,再加上eval在从新构建时比较快,因此cheap-module-eval-source-map确实是一个不错的选择。
另外,可别把source-map带到线上去了呀,那会给人人带来视奸你代码的机会。
最后,当在开发模式下debug代码时,要记得感谢sourcemap和浏览器的支持。每个小小的细节和好用之处,都离不开前人长久的努力呀。