深刻了解 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

相關文章
相關標籤/搜索