想必你們都對這一段截圖耳熟能詳: html
由於檢測並加載了sourcemap,因此能夠直接定位到編譯前代碼的特定位置。因此用一句話解釋sourcemap很簡單,就是一段維護了先後代碼映射關係的json描述文件。但若是細究其內容,能夠泡杯茶說一段故事了~html5
在2009年google的一篇 文章 中,在介紹Cloure Compiler(一款js壓縮優化工具,可類比於uglify-js)時,google也順便推出了一款調試工具:firefox插件Closure Inspector,以方便調試編譯後代碼。這就是sourcemap的最初代啦!webpack
You can use the compiler with Closure Inspector , a Firebug extension that makes debugging the obfuscated code almost as easy as debugging the human-readable source.git
2010年,在第二代即 Closure Compiler Source Map 2.0 中,sourcemap肯定了統一的json格式及其他規範,已幾乎具備如今的雛形。最大的差別在於mapping算法,也是sourcemap的關鍵所在。第二代中的mapping已決定使用base 64編碼,可是算法同如今有出入,因此生成的.map相比如今要大不少。es6
2011年,第三代即 Source Map Revision 3.0 出爐了,這也是咱們如今使用的sourcemap版本。從文檔的命名看來,此時的sourcemap已脫離Clousre Compiler,演變成了一款獨立工具,也獲得了瀏覽器的支持。這一版相較於二代最大的改變是mapping算法的壓縮換代,使用VLQ編碼生成base64前的mapping,大大縮小了.map文件的體積。github
Sourcemap發展史的有趣之處在於,它做爲一款輔助工具被開發出來。最後它輔助的對象日漸式微,而它卻成爲了技術主體,被寫進了瀏覽器中。web
{
"version": 3,
"sources": ["test.es6.js"],
"names": [],
"mappings": ";;AAAA,IAAM,MAAM,GAAG,SAAT,MAAM,CAAI,CAAC;SAAK,CAAC,GAAG,CAAC;CAAA,CAAC",
"file": "test.js",
"sourcesContent": ["const square = (x) => x * x;"]
}
複製代碼
這就是一段簡單的map文件,其中算法
接下來,就來詳細瞅瞅主要元素mapping把!json
據 html5rocks的sourcemap教程 (這也是最經典的一篇sourcemap博文了)所說,數組
sourcemap v1最開始生成的sourcemap文件大概有轉換後文件的10倍大。sourcemap v2將之減小了50%,v3又在v2的基礎上減小了50%。因此目前133k的文件對應的sourcemap文件大小大概在300k左右。
那麼它是如何用最精簡的方式維護現文件到源文件的映射的呢?對於整個映射而言,重要的幾要素無非是「原文件、原行數、原列數、原變量名」,因此對映射算法的優化,實質也就是對這一段數據的存儲優化。
我發現了一篇很是好的文章How do source maps work,做者經過簡單的示例解釋了map的大體工做原理。接下來我會介(梗)紹(概)下做者的幾個要點。
原代碼:
const square = (x) => x * x;
複製代碼
Babel編譯後代碼:
"use strict";
var square = function square(x) {
return x * x;
};
//# sourceMappingURL=test.js.map
複製代碼
Mappings(接下來的分析都會以這份mapping爲準喔):
";;AAAA,IAAM,MAAM,GAAG,SAAT,MAAM,CAAI,CAAC;SAAK,CAAC,GAAG,CAAC;CAAA,CAAC"
複製代碼
如上,能夠看到這段mappings有幾個關鍵元素:分號;
逗號,
及分隔出的一串一眼看不懂的字母。其中:
附:【base64 VLQ編碼 - 十進制轉換表 】(先僅供參考,具體轉換規則見下文VLQ編碼部分)
一段segment中的VLQ編碼,其實既能夠是4位,也能夠是5位。每一位都有特定含義:
帶着解釋,再看如下兩個問題
上述mapping以;;開頭,是由於babel編譯後新增的use strict及下面空行沒有隊對應的原文~
[AAAA,IAAM] 轉換爲VLQ編碼是[0000, 4006]。第二個segment裏的4表明square在轉換後文件的第4列,而6表明square在現文件的第6列。
0|1|2|3|4|
v|a|r| |s|q|u|a|r|e|
0|1|2|3|4|5|6|
c|o|n|s|t| |s|q|u|a|r|e|
複製代碼
以上,相信再看見一段mappings時,不說能立馬知道它表明了哪一段源文件,但至少不會兩眼一抹黑了~
其實從以上過程讀來,在知道了規則的狀況下,反編譯一段編碼老是容易的。難的實際上是創造和優化規則的過程。好比將源文件名放在一個數組中,經過存儲原文件在數組中的index,而不是源文件name自己;好比存儲的column index是相對位置而不是絕對位置;好比經過;分隔行經過,分隔列,而不是記錄行號和列號;好比爲何選擇了經base64轉換的VLQ編碼…正是這些微小的一點點比對出來的細節,才能積少成多,將mapping文件最大化的壓縮。
要了解VLQ編碼,原本我是拒絕的…我也許永遠也不會用到…可是一想來都來了,不如也順便了解一下叭!就當作陶冶情操呢~
So..瞭解完成後,感受文本編碼和網絡技術一般會講到傳遞xx包要怎麼封裝一串數據同樣,重點都是差很少的。一是如何分割數字(即表示連續),好比1和17在一塊兒組成117的時候,怎麼來表示這是1和17而不是一百一十七。二是如何表示正負。
一個VLQ小節有6位(因此恰好適合經過base64轉換成字符呀),第一位表明連續,最後一位通常表明正負,因此中間至少有4位能夠表示value。
如上,能夠看到-15~15均可以用一個VLQ小節來表示。當|Number|>15時,則須要用到多個VLQ小節。另外,正負只需用第一小節的最後一位表示。因此多個小節時,第>1個小節的第6位能夠不表明正負,也表明內容。
若是本身有須要使用VLQ編碼的話,則能夠參考市面上現成的庫~好比GitHub - Rich-Harris/vlq
用webpack的人都知道,它有好多種sourcemap選項Devtool | webpack。在沒了解sourcemap的深層本質以前,我對這幾個選項都只知其一;不知其二,就死記硬揹着cheap-module-eval-source-map是最優解╮( ̄▽ ̄")╭
其實這幾個選項,都是關鍵字cheap, module, eval, inline, hidden + sourcemap的排列組合,只要清楚各自的做用及意義就行了。
對我而言,行數必需列數沒必要需,映射loader編譯前的代碼也必需,再加上eval在從新構建時比較快,因此cheap-module-eval-source-map確實是一個不錯的選擇。
另外,可別把source-map帶到線上去了呀,那會給人人帶來視奸你代碼的機會。
最後,當在開發模式下debug代碼時,要記得感謝sourcemap和瀏覽器的支持。每個小小的細節和好用之處,都離不開前人長久的努力呀。