深刻了解 sourceMap

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

怎么才能深刻的了解呢,那就是深刻灵魂,基于 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 模式想在某行加断点却加不上的情景😊。

Refs

相关文章
相关标签/搜索