SourceMap介紹

1、前言

隨着前端代碼越開越複雜的狀況下,開發者一般會使用webpack、UglifyJS2等工具對代碼進行打包變換,這樣能夠減小代碼大小,有效提升訪問速度,同時還可以有效的保護源代碼不被別人獲取。javascript

正常代碼hello.js: 
function sayHello() { 
	var name = "Fundebug"; 
	var greeting = "Hello, " + Name; 
	console.log(greeting); 
} 
sayHello(); 

>>>>>>>>>>使用UglifyJS2對源碼進行壓縮 start>>>>>>>>>>>> 
uglifyjs hello.js \ 
	-m toplevel=true \ 
	-c unused=true,collapse_vars=true \ 
	-o hello.min.js 
>>>>>>>>>>使用UglifyJS2對源碼進行壓縮 end>>>>>>>>>>>> 

壓縮後的代碼hello.min.js: 
function o(){var o="Hello, "+Name;console.log(o)}o(); 

複製代碼

然而壓縮代碼的報錯信息是很難Debug的,由於它的行號和列號已經失真。
執行hello.js的報錯信息,咱們可以直接定位到出錯的位置。 html

而執行壓縮後的hello.min.js的報錯信息,
前端

對比壓縮先後的出錯信息,咱們會發現,錯誤行號和列號已經失真,且函數名也通過了變換。而對於真實的前端項目,開發者會將數十個源文件壓縮爲一個文件,這時,錯誤的列號可能多達數千,且出錯的真實文件名也是很難肯定的,這樣的話,壓縮代碼的報錯信息是很難Debug的。
而Source Map則能夠用於還原真實的出錯位置,幫助開發者更快的Debug。java

2、什麼是SourceMap

簡單說,Source map就是一個信息文件,裏面儲存着位置信息。也就是說,轉換後的代碼的每個位置,所對應的轉換前的位置。
有了它,出錯的時候,除錯工具將直接顯示原始代碼,而不是轉換後的代碼。這無疑給開發者帶來了很大方便。webpack

一、生成SourceMap文件

目前各類主流前端任務管理工具,打包工具都支持生成Source Map,具體能夠查看生成SourceMap
使用UglifyJS2時指定source-map選項便可生成Source Map:web

>>>>>>>>>>使用UglifyJS2生成SourceMap start>>>>>>>>>>>> 
uglifyjs hello.js \ 
	-m toplevel=true \ 
	-c unused=true,collapse_vars=true \ 
	--source-map url='hello.min.js.map' \ 
	-o hello.min.js 
>>>>>>>>>>使用UglifyJS2生成SourceMap end>>>>>>>>>>>>

添加了SourceMap的hello.min.js:
function o(){var o="Hello, "+Name;console.log(o)}o();
//# sourceMappingURL=hello.min.js.map

生成的SourceMap文件hello.min.js.map:
{
 "version": 3,
 "sources": [
 "hello.js"
 ],
 "names": [
 "sayHello",
 "greeting",
 "Name",
 "console",
 "log"
 ],
 "mappings": "AAAA,SAASA,IACL,IACIC,EAAW,UAAYC,KAC3BC,QAAQC,IAAIH,GAEhBD"
}
複製代碼

二、SourceMap文件解析

由hello.min.js.map可知,Source Map是一個JSON文件,而它包含了代碼轉換先後的位置信息。也就是說,給定一個轉換以後的壓縮代碼的位置,就能夠經過Source Map獲取轉換以前的代碼位置,反過來也同樣。Source Map各個屬性的含義以下:數組

  • version:Source Map的版本號。
  • sources:轉換前的文件列表。該項是一個數組,表示可能存在多個文件合併。
  • names:轉換前的全部變量名和屬性名。
  • mappings:記錄位置信息的字符串,通過編碼。
  • file:(可選)轉換後的文件名。
  • sourceRoot:(可選)轉換前的文件所在的目錄。若是與轉換前的文件在同一目錄,該項爲空。
  • sourcesContent:(可選)轉換前的文件內容列表,與sources列表依次對應。

Source Map真正神奇之處在於mappings屬性,它記錄了位置是如何對應的。bash

三、mappings屬性

sourcemap的mappings屬性的值是將位置映射關係採用Base 64 VLQ編碼後生成的字符串。
mappings屬性值包含的信息能夠按照下面的方式進行拆分:app

  1. 第一層是行對應,以分號(;)標識編譯後代碼的每一行,便是分號間隔的內容表明編譯後代碼的一行;
  2. 第二層是位置對應,以逗號(,)標識編譯後代碼該行中的每個映射位置,便是逗號間隔的內容表明一個映射位置;
  3. 第三層是位置轉換,以5組VLQ編碼字段標識源碼和編譯後代碼的具體映射信息。從左至右每組表示以下:
    • 第1組,表示對應編譯後代碼的第幾列(從第0列開始計算);
    • 第2組,表示源碼所屬文件在sources數組中的索引值;
    • 第3組,表示對應源碼的第幾行(從第0行開始計算);
    • 第4組,表示對應源碼的第幾列(從第0列開始計算);
    • 第5組,表示在names數組中的索引值,若沒有則可省略。

注意:每組VLQ編碼字段有0~N個VLQ編碼字符組成,如 qC | A | A | U | H。
例如「AAAAA」表示的就是:該位置在轉換後代碼的第0列,對應sources屬性中第0個文件,屬於轉換前代碼的第0行第0列,對應names屬性中的第0個變量。函數

四、VLQ編碼

VLQ編碼最先用於MIDI文件,後來被多種格式採用。它的特色就是能夠很是精簡地表示很大的數值。它規定,每一個字符使用6個兩進制位。
以下圖所示:

  1. 在這6個位中,左邊的第一位(最高位)表示是否"連續"(continuation)。若是是1,表明這6個位後面的6個位也屬於同一個數;若是是0,表示該數值到這6個位結束。
  2. 這6個位中的右邊最後一位(最低位)的含義,取決於這6個位是不是某個數值的VLQ編碼的第一個字符。若是是的,這個位表明"符號"(sign),0爲正,1爲負(Source map的符號固定爲0);若是不是,這個位沒有特殊含義,被算做數值的一部分。

上面看的可能比較迷糊,咱們看一個例子,如何對數值137進行VLQ編碼:

  • 先把 137 轉換成二進制:10001001
  • 因爲 137 是正數,因此在最低位補0 變成 100010010
  • 按照 5bit 一組的方式分組,變成 01000 10010
  • 按照從低位到高位的順序,以 5bit 一組爲單位,依次拿出數據作轉換。
  • 先拿出第一組 10010 , 由於後面還有數據,因此須要在高位補 1,變成 110010 ,編碼成 Base64: y
  • 再拿出第二組,也是咱們這裏的最後一組,01000 , 因爲是最後一組,因此在高位補 0 變成 001000,編碼成 Base64: I
  • 按照從低位到高位的順序把這些 ASCII 字符拼接起來,變成 yI

注意:轉換爲二進制碼以後,是從低位到高位的進行Base64轉換。

下圖爲base64的映射表:


根據以上對VLQ編碼的方式,咱們可以掌握瞭如何將一個數值進行base64 VLQ編碼,瞭解了編碼以後,解碼也沒必要在講解了,倒推的回去就好了。這裏推薦一個在線的VLQ編解碼網站: Base64VLQ在線編解碼網站

五、mappings位置數值解讀

咱們瞭解完了VLQ的編解碼以後,咱們將開頭生成的SourceMap文件的mappings屬性分析一下看看是否可以一一對應。
開頭生成的SourceMap文件的mappings屬性爲:

"mappings": "AAAA,SAASA,IACL,IACIC,EAAW,UAAYC,KAC3BC,QAAQC,IAAIH,GAEhBD"
複製代碼

咱們使用Base64VLQ在線編解碼網站把內容解碼一下:

[0,0,0,0], [9,0,0,9,0], [4,0,1,-5], [4,0,1,4,1], [2,0,0,11],
[10,0,0,12,1], [5,0,1,-27,1], [8,0,0,8,1], [4,0,0,4,-3], [3,0,2,-16,-1]
複製代碼

注意:mappings的位置值都是相對位置,每一個值都是相對於前一個位置值的。(當文件內容巨大時,精簡後的編碼也有可能會由於數字位數的增長而變得很長,同時,處理較大數字老是不如處理較小數字容易和方便。因而mappings中的位置記錄是記錄的這些位置的相對值。)
所以每個位置值加上前一個位置值能夠獲得:

[0,0,0,0], [9,0,0,9,0], [13,0,1,4], [17,0,2,8,1], [19,0,2,19],
[29,0,2,31,2], [34,0,3,4,3], [42,0,3,12,4], [46,0,3,16,1], [49,0,5,0,0]
複製代碼

整理一下能夠得出以下結果:

(a,b)=>name(m,n)表示生成代碼中的a行b列對應原始代碼中的m行n列的位置,而且在原始代碼中這個位置的變量名是name。

(0,0)=>(0,0)
(0,9)=>sayHello(0,9)
(0,13)=>(1,4)
(0,17)=>greeting(2,8)
(0,19)=>(2,19)
(0,29)=>Name(2,31)
(0,34)=>console(3,4)
(0,42)=>log(3,12)
(0,46)=>greeting(3,16)
(0,49)=>(5,0)
複製代碼

參考:

SourceMap詳解--阮一峯
SourceMap詳解--網易雲
Base64 VLQ編碼規則

相關文章
相關標籤/搜索