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 模式想在某行加斷點卻加不上的情景😊。