爲啥用sourceMap
這幾天在搞前端錯誤日誌,做過線上發佈的都知道,我們發佈到生產環境的代碼,一般都有如下步驟:
- 壓縮混淆,減小體積
- 多個文件合併,減少
HTTP
請求數 - 通過編譯或者轉譯,將其他語言編譯成
JavaScript
這三個步驟,都使得實際運行的代碼不同於開發代碼,不管是 debug 還是捕獲線上的報錯,都會變得困難重重。
解決這個問題的方法,就是使用sourceMap
。
啥是sourceMap
簡單說,sourceMap
就是一個文件,裏面儲存着位置信息。
仔細點說,這個文件裏保存的,是轉換後代碼的位置,和對應的轉換前的位置。
有了它,出錯的時候,通過斷點工具可以直接顯示原始代碼,而不是轉換後的代碼。
sourceMap長啥樣
通過webpack
等工具,我們可以使用 sourceMap
,這裏不細說配置方法,可以看這裏
sourceMap
是一個map
文件,與源碼在同一個目錄下。
在壓縮代碼的最後一行,會有這樣的一個引用:
//# sourceMappingURL=app.js.map
指向的就是我們的map
文件。
sourceMap
的格式如下:
{
version : 3, //SourceMap的版本,目前爲3
sources: ["foo.js", "bar.js"], //轉換前的文件,該項是一個數組,表示可能存在多個文件合併
names: ["src", "maps", "are", "fun"], //轉換前的所有變量名和屬性名
mappings: "AACvB,gBAAgB,EAAE;AAClB;", //記錄位置信息的字符串
file: "out.js", //轉換後的文件名
sourcesContent: " \t// The module cache\n", //轉換後的代碼
sourceRoot : "" //轉換前的文件所在的目錄。如果與轉換前的文件在同一目錄,該項爲空
}
其他的都很好解釋,我們詳細說一下mappings
屬性。
mappings
以"AACvB,gBAAgB,EAAE;AAClB;"
爲例:
- 每個分號對應轉換後源碼的一行;
- 每個逗號對應轉換後源碼的一個位置;
-
AACvB
代表該位置轉換前的源碼位置,以VLQ
編碼表示;
位置對應的原理
位置關係的保存經歷了諸多步驟和優化,這個不詳細說了,想看的可以看這裏,我們只說最後的結果。
在每個位置中:
- 第一位,表示這個位置在【轉換後代碼】的第幾列。
- 第二位,表示這個位置屬於【sources屬性】中的哪一個文件。
- 第三位,表示這個位置屬於【轉換前代碼】的第幾行。
- 第四位,表示這個位置屬於【轉換前代碼】的第幾列。
- 第五位,表示這個位置屬於【names屬性】的哪一個變量。
舉例
假設現在有a.js
,內容爲feel the force
,處理後爲b.js
,內容爲the force feel
以the
爲例,它在輸出中的位置是(0,0),a.js
是sources
的第1個(這裏只是舉例),輸入中的位置是(0,5),the
是names
的第2個(這裏只是舉例)。
那麼映射關係爲:
0 1 0 5 2
最後將 01052 表示爲 Base64 VLQ 即可。
說明:
- 所有的值都是以0作爲基數
- 第五位不是必需的,如果該位置沒有對應
names
屬性中的變量,可以省略第五位 - 每一位都採用
VLQ
編碼表示,由於VLQ
編碼是可變長的,所以每一位可以由多個字符構成 - 爲什麼不保存轉換後代碼的行號,因爲我們輸出的文件總是一行,這樣輸出的行號就可以省略,因爲都是0,沒必要寫出來
- 對於輸出後的位置來說,到後邊會發現它的列號特別大,爲了避免這個問題,採用相對位置進行描述
相對位置是啥呢,看示意圖:
第一次記錄的輸入位置和輸出位置是絕對的,往後的輸入位置和輸出位置都是相對上一次的位置移動了多少,例如the
的輸出位置爲(0,-10),因爲the
在feel
的左邊數10下才能到這個位置。
VLQ編碼
VLQ
是Variable-length quantity
的縮寫,是一種通用的、使用任意位數的二進制來表示一個任意大的數字的一種編碼方式。這種編碼最早用於MIDI文件,後來被多種格式採用,它的特點就是可以非常精簡地表示很大的數值,用來節省空間。
這種編碼需要用最高位表示連續性,如果是1,代表這組字節後面的一組字節也屬於同一個數;如果是0,表示該數值到這就結束了。
這樣乾巴巴說不太容易懂,還是舉個栗子說明一下吧。
如何對數值137進行VLQ編碼:
步驟 | 結果 |
---|---|
將137改寫成二進制形式 | 10001001 |
七位一組做分組,不足的補0 | 0000001 0001001 |
最後一組開頭補0,其餘補1 | 10000001 00001001 |
所以,137的VLQ編碼形式爲10000001 00001001
Base64 VLQ
與一般的VLQ
的區別:
- 一個
Base64
字符只能表示6bit(2^6)
的數據 -
Base64 VLQ
需要能夠表示負數,於是用最後一位來作爲符號標誌位。 - 由於只能用6位進行存儲,而第一位表示是否連續的標誌,最後一位表示正數/負數。中間只有4位,因此一個單元表示的範圍爲
[-15,15]
,如果超過了就要用連續標識位了。
表示正負的方式:
- 如果這組數是某個數值的
VLQ
編碼的第一組字節,那它的最後一位代表"符號",0爲正,1爲負; - 如果不是,這個位沒有特殊含義,被算作數值的一部分。
我們再來舉個栗子說明下使用方法。
如何對數值137進行Base64 VLQ
編碼:
步驟 | 結果 |
---|---|
將137改寫成二進制形式 | 10001001 |
127是正數,末位補0 | 100010010 |
五位一組做分組,不足的補0 | 01000 10010 |
將組倒序排序 | 10010 01000 |
最後一組開頭補0,其餘補1 | 110010 001000 |
轉64進制 | y和I |
所以 137 通過Base64 VLQ
表示爲yl
可以看出:
- 在
Base64 VLQ
中,編碼順序是從低位到高位 - 而在
VLQ
中,編碼順序是從高位到低位