sourceMap
于今前端er来说,已经再也不陌生,由于咱们随手都能写出比较超前的代码,为了更好的运行这些代码,咱们的代码就会通过各类加工处理,这使得实际运行中的代码跟咱们手写的代码存在着很大的gap。时光冉冉,咱们终会产生根据报错的行和列找到原文件行和列的需求。sourceMap 就是这解决问题的关键,它就记录着已转换代码
和源代码
的映射关系。一旦咱们知道报错的行列号,经过 sourceMap 咱们就能找出源代码的对应位置。javascript
在浏览器中怎么使用的?前端
- 生产一个source map
- 加入一个注释在转换后的文件,它指向source map。注释的语法相似
一个 sourceMap 的基本结构以下java
{
"version": 3, // sourcemap 版本号
"file": "main.js", // 转换后的文件名
"sources": ["a.js", "b.js"], // 源文件列表,一个文件多是合并多个小文件而来
"names": [], // 原变量名和属性名,好比使用 uglify 等压缩工具,会修改变量名
"mappings": ";;AAAA,IAAM", // 映射关系
"sourceContent": ["..."] // 源代码(可能没有)
}
复制代码
怎么才能深刻的了解呢,那就是深刻灵魂,基于 sourceMap 的做用,咱们能够提取出4个关键信息:转换后文件的行(r)、转换后文件的列(c)、原文件的行(r‘)、原文件的列(c’)。所以 soucemap 文件就必然存储着这种映射关系,看看上面的文件结构中的属性名,没错 mappings
就储存着这些关键信息。git
我用tsc
编译了一段代码,来作具体分析:github
源代码(index.ts):算法
class Hello {
constructor() {}
}
复制代码
编译成ES5以后的代码(index.js):typescript
"use strict";
var Hello = /** @class */ (function () {
function Hello() {
}
return Hello;
}());
//# sourceMappingURL=index.js.map
复制代码
sourceMap文件(index.js.map):json
{
"version": 3,
"file": "index.js",
"sources": [
"index.ts"
],
"names": [],
"mappings": ";AAAA;IACE;IAAe,CAAC;IAClB,YAAC;AAAD,CAAC,AAFD,IAEC"
}
复制代码
mappings
的值就是一个字符串,它包含了一堆字母,还有;
和,
这两个符号,非常有规律。其中的原理呢,就是字符串经过;
和,
的切割,就能拿到行
、列
的对应关系了:经过 ;
切割出每行的信息,再用 ,
切割行信息获得列信息。也就意味着:经过数;
的个数,就能肯定编译后文件的行号r
,经过解析 ,
切割出来的数据,就能获得列号 c
、原文件行 r'
、原文件列 c'
。浏览器
在这个实际的例子,咱们能够看到第一个;
号前是空的,返观上面的代码,咱们能发现 "use strict"
,在原文件中的确没有,所以第一行没有任何映射关系。那么后面的 AAAA
,怎么获得其中的信息呢,这段字符实际上是被一种压缩算法压缩了,咱们只要找到对应的算法解码就行。这个算法就是 VLQ
了,咱们直接用现成的库来解码就行 AAAA => 0, 0, 0, 0
,诶?怎么会有四个数字呢,我来解释一下每个数字的含义:markdown
第一位: 表明在转换后文件的第几列 (同行相对上一个列的偏移量)
第二位: 表明源文件在
sources
里对应的下标第三位: 表明在源文件的第几行(相对于上一个坐标行的偏移量)
第四位: 表明在源文件的第几列(相对于上一个坐标列的偏移量)
咱们翻译过来就是 index.js [1, 0] --- index.ts [0, 0]
,下面直接进行超能力翻译了:
第0行
第1行 AAAA => 0, 0, 0, 0 => index.js [1, 0] --- index.ts [ 0, 0];
第2行 IACE => 4, 0, 1, 2 => index.js [2, 4] --- index.ts [(0 + 1) = 1, ( 0 + 2) = 2];
第3行 IAAe => 4, 0, 0, 15 => index.js [3, 4] --- index.ts [(1 + 0) = 1, ( 2 + 15) = 17],
第3行 CAAC => 1, 0, 0, 1 => index.js [3, (4 + 1) = 5] --- index.ts [(1 + 0) = 1, (17 + 1) = 18];
第4行 IAClB => 4, 0, 1, -18 => index.js [4, 4] --- index.ts [(1 + 1) = 2, (18 - 18) = 0],
第4行 YAAC => 12, 0, 0, 1 => index.js [4, (4 + 12) = 16] --- index.ts [(2 + 0) = 2, ( 0 + 1) = 1];
第5行 AAAD => 0, 0, 0, -1 => index.js [5, 0] --- index.ts [(2 + 0) = 2, ( 1 - 1) = 0],
第5行 CAAC => 1, 0, 0, 1 => index.js [5, (0 + 1) = 1] --- index.ts [(2 + 0) = 2, ( 0 + 1) = 1],
第5行 AAFD => 0, 0, -2, -1 => index.js [5, (1 + 0) = 1] --- index.ts [(2 - 2) = 0, ( 1 - 1) = 0],
第5行 IAEC => 4, 0, 2, 1 => index.js [5, (1 + 4) = 5] --- index.ts [(0 + 2) = 2, ( 0 + 1) = 1]
复制代码
经过上上面的数字含义的,结合我在上面用括号计算过程当中加入的括号,不知道你们有没有看明白。编译后文件的行号,咱们直接能够获取到,每行的第一个列数据能够直接使用,可是若是这一行里面有多个列数据,那么就须要从左往右依次计算,公式就是前一个列号+偏移量
(每行独立计算),若是是第一个列数据,那前列号就是0
,反复分析第3行和、第4行和第5行,能够看出端倪。源文件的行号
和列号
须要从第一个计算出来的点开始,按顺序一直加上偏移量(偏移量也有多是负数,好比第4行第一个列数据)。
咱们能够发现,解析列信息并非想象中那么简单,须要经过必定的算法计算才能得到正确的信息,这也是 sourceMap 迭代到第三版的成果,这里面最大的目的就是为了压缩 sourceMap 的体积,但只要咱们了解其生成的原理,sourceMap 也就不神秘了,看到这里,我相信你也能够徒手分析 sourceMap 了。
sourceMap 本质上仅仅只是一种位置映射关系,咱们能够根据一个坐标,反推出源文件的坐标,但它并不能在运行环境中创建对应的变量映射关系,好比源代码 const name = 1;
, 被转换成了 var a = 1;
,咱们在 debug 的时候,若是直接把鼠标放在源代码中的 name
这个变量上,咱们并不会获得变量值的提示。因为某些缘由,转换后的代码和源代码,并非每一行,每一列都是有对应关系(最多见的是 tree shaking
它会删除一些代码),固然咱们也会赶上在 debug 模式想在某行加断点却加不上的情景😊。