聊一聊 SourceMap

前言

我們項目的代碼在經過編譯打包後,會將開發時多個文件的代碼合併到同一份文件中,而且還會經過各種壓縮,合併,代碼醜化等等操作,轉換完最終生成的代碼纔會用於線上環境,所以我們線上實際運行的代碼跟我們開發時的代碼是有非常大的不同,如果此時出現了 bug,那麼我們只能定位到轉換後代碼的位置,但此時的代碼已經面目全非了

轉換後的代碼類似下面這樣

雖然這種代碼對計算機非常友好,但是我們 debug 將會變得很困難,這時候就需要 sourcemap 了

什麼是 SourceMap

簡單來說,Sourcemap 就是一個信息文件,它裏面存儲着代碼轉換前後的對應位置信息,也就是轉換壓縮後的代碼所對應的轉換前的源代碼位置,是源代碼和生產代碼的映射, Sourcemap 解決了在打包過程中,代碼經過壓縮,去空格以及 babel 編譯轉化後,由於代碼之間差異性過大,debug 困難的問題

大家的項目在開發完進行 build 後,在打包文件夾裏除了有 js,css,圖片等資源,一定還見過 .js.map 文件,這種就是 sourcemap 文件

點開一個打包後的 js 文件,拉到最後一行,可以看到 //# sourceMappingURL=main.js.map

有了這行,就可以啓用 sourcemap,這個 sourceMappingURL 就是標記了該文件的 sourcemap 的地址,這個 sourcemap 文件可以放在本地,也可以放在網絡上

再點開一個 .js.map 文件看看,有一堆類似亂碼的東西,後面看一個簡單一點的

SourceMap 的生成

生成的方法有很多,而且有很多前端的工具都支持,如 webpack,uglifyjs,gulp 等,這裏就不詳細講述了,對怎麼生成 sourcemap 感興趣可以看這個文章

https://code.tutsplus.com/tutorials/source-maps-101--net-29173

SourceMap 的屬性

我們看一個簡單一點的代碼

const value = 123;
console.log(value);

用 webpack 打包後的代碼

console.log(123);
//# sourceMappingURL=bundle.js.map

生成的 sourcemap 文件

{
   version : 3,
   file :  bundle.js ,
   mappings :  AACAA,QAAQC,IADM ,
   sources : [
     webpack://studysourcemap/./test.js 
  ],
   sourcesContent : [
     const value = 123;\nconsole.log(value); 
  ],
   names : [
     console ,
     log 
  ],
   sourceRoot :   
}

每個屬性的含義如下

SourceMap 的版本

關於 sourcemap 的版本

第一版生成的 map 文件大概有轉化後文件的 10 倍大,第二版則將體積減少了 20%~30%,第三版又在 v2 的基礎上體積減少了一半

正是因爲有了第三代 Source Map Revision 3 Proposal 這個標準,不同的打包工具和瀏覽器才能使用 sourcemap,github 上的一個根據這個標準生成 sourcemap 的庫 https://github.com/mozilla/source-map

SourceMap 的原理

這裏主要關注 mappings 和 names 屬性,mappings 屬性是一個很長的字符串,它分成三個部分

  1. 分號 (;),表示行對應,生成的文件的每一行用分號(;) 分隔,一個分號代表轉換後源碼的一行

  2. 逗號 (,),位置對應,每一段用逗號(,) 分隔,一個逗號對應轉換後源碼的一個位置

  3. 英文字母,每一段由 1,4 或 5 塊可變長度的字段組成,記錄原始代碼的位置信息

舉一個簡單的例子,假設有如下的 mappings 屬性

 mappings :  AACAA;QAAQC,IADM ,

有一個分號,說明有兩行代碼,分號前 AACAA 是第一行,後面 QAAQC,IADM 是第二行

第二行有一個逗號,說明這一行分爲兩段,QAAQCIADM

分號跟逗號大家應該都沒什麼疑問,主要就是英文字母這一塊的意義位置對應的原理

每一段最多有 5 個部分

那麼這五個部分是怎麼來的,我們一步一步來看

假設文件 a.js 有一行代碼 I Love SourceMap,最終打包後輸出的文件爲 bundle.js,內容爲 Javascript is awesome,如下

那麼怎麼表示映射關係

以 Love 爲例,它原始的位置爲 (0,2),輸出後是 awesome,位置爲 (0,14),那麼我們可以這樣來映射

像這樣寫成一種固定的格式,裏面包含了原始位置和輸出後的位置,單詞,同時還有原始文件名,因爲可能把多個文件進行處理輸出,如果不寫文件名,就不知道輸入位置來自哪個文件

9bfbcn

我們可以優化一下,把 a.js 和最後面的單詞提出來各放到一個數組裏,用 sources 記錄所有的原始文件名,names 記錄原始文件中的所有單詞,然後用下標表示他們,以 Love 爲例,就變成

很多時候,我們輸出的文件其實是隻有一行的,所以可以把輸出文件的行號省略掉,就變成

考慮到,如果文件特別大的話,那麼行列的數值可能會特別大,所以可以考慮用相對位置來代替絕對位置來表示,只用絕對位置表示第一個單詞的位置,後面的都使用相對前一個單詞的位置

KWs9ZW

所以我們現在可以得到這麼一個初步的 map 文件

{
    names: ['I''Love''SourceMap'],
    sources: ['a.js'],
    mappings: [11|0|0|0|0, 3|0|0|2|1, -14|0|0|5|2]
}

但是 mappings 這裏十分難看,而且還需要用來分隔,多佔一個位置,用 vlq 編碼就可以解決分隔數字的問題,他的核心思路是在連續的數字上做標記,我們先來理解一下,拿上面 mappings 屬性的第一個爲例,去掉,然後在連續的字符上加上一個標記

110000

從左往右開始讀取,數字 1 有標記,說明還有連續,再取下一個,是 1,這個 1 沒被標記,第一個數結束,所以第一個數是 11

繼續往下,0 沒被標記,說明是一個完整的數字,第二個數就是 0

依此類推。。。

最終就能得到 11,0,0,0,0

而 vlq 利用 6 位二進制數進行存儲,其中第一位就表示是否連續的標識位,最後一位表示正數還是負數(0 正數,1 負數) ,中間只有 4 位,因此一個單元表示的範圍爲 [-15,15],如果超過了就要用連續標識位了

看幾個例子來理解,每一步的變化我都用不同顏色標記了

第三步(按...5554 分割),最右邊 4 位是因爲他還需要額外多表示一位符號位,其餘的都可以用 5 位來表示數值

倒數第二步倒順序,是因爲 VLQ 表示數據字節組的順序是倒過來的

最終我們可以得到他們的 vlq 編碼

FbewdK

然後再把它轉成 base64 編碼,可以查下面這張表

就可以得到 5 和 - 19 的 base64 vlq 編碼了,因爲 5 的 vlq 編碼數值是 10,所以查上表可得到 K,同理 - 19 可以得到 n 和 B,最終能得到 5 和 - 19 的 base64 vlq 編碼分別是 K 和 nB

這裏有一個網站可以自己轉換驗證一下 https://www.murzwin.com/base64vlq.html

然後我們回過頭爲我們最開始那個簡單的 js 文件手動生成一下 map 文件來驗證一下

const value = 123;
console.log(value);

打包後的代碼

console.log(123);
//# sourceMappingURL=bundle.js.map

sources 和 names 是可以先確定好的

{
     sources : [ a.js ],
     names : [ console ,  log ],
}

再得到 base64 vlq 編碼

1WcgmI

所以我們可以得到最終的 map 文件

{
     sources : [ a.js ],
     names : [  console ,  log ],
     mappings :  AACAA,QAAQC,IADM ,
    // ...其他的
}

反過來也能根據 sourcemap 文件推出原始的位置,這裏就不再演示了

SourceMap 總結

感謝巨人

https://juejin.cn/post/7023537118454480904

https://juejin.cn/post/6963076475020902436

https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.1ce2c87bpj24

https://www.ruanyifeng.com/blog/2013/01/javascript_source_map.html

https://www.html5rocks.com/en/tutorials/developertools/sourcemaps/

http://www.qiutianaimeili.com/html/page/2019/05/89jrubx1soc.html

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/YVySzeCNpQpijwf8kwblFw