絕了,沒想到一個 source map 竟然涉及到那麼多知識盲區

Source map 想必你們都不陌生。線上的代碼可能是壓縮後的,若是線上有報錯卻只能調試那個代碼多半是個噩夢。所以咱們須要有一個橋樑幫助咱們搭建起源代碼及壓縮後代碼的聯繫,source map 就是起了這個做用。html

如下是 MDN 對於 source map 的解釋:html5

調試原始源代碼會比瀏覽器下載的轉換後的代碼更加容易。 source map 是從已轉換的代碼映射到原始源的文件,使瀏覽器可以重構原始源並在調試器中顯示重建的原始源。webpack

可是不知道各位讀者有沒有對 source map 的原理產生過疑問?筆者列出了四個疑問,不知道各位是否是也存在過這樣的問題:git

Source map 四問

接下來的內容會逐步爲讀者解答這四問。github

source map 文件是否影響網頁性能

這個答案確定是不會影響,不然構建相關的優化就確定會涉及到對於 source map 的處理了,畢竟 source map 文件也不小。web

其實 source map 只有在打開 dev tools 的狀況下才會開始下載,相信大部分用戶都不會去打開這個面板,因此這也就不是問題了。json

這時可能會有讀者想說:哎,可是我好像歷來沒有在 Network 裏看到 source map 文件的加載呀?其實這只是瀏覽器隱藏了而已,若是你們使用抓包工具的話就能發如今打開 dev tools 的時候開始下載 source map 了。數組

source map 存在標準嘛?

source map 是存在一個標準的,爲 Google 及 Mozilla 的工程師制定,文檔地址。正是由於存在這份標準,各個打包器及瀏覽器才能生成及使用 source map,不然就亂套了。瀏覽器

各個打包器基本都基於該庫來生成 source map,固然也存在一些魔改的方案,可是標準都是統一的。markdown

經過上面的庫生成出來的 source map 格式大體以下,你們也能夠對比各個打包器的產物,格式及內容大部分都是一致的:

{
  version: 3,
  file: "min.js",
  names: ["bar", "baz", "n"],
  sources: ["one.js", "two.js"],
  sourceRoot: "http://example.com/www/js/",
  mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA"
}
複製代碼

接下來筆者介紹下重要字段的做用:

  • version:顧名思義,指代了版本號,目前 source map 標準的版本爲 3,也就是說這份 source map 使用的是第三版標準產出的
  • file:編譯後的文件名
  • names:一個優化用的字段,後續會在 mappings 中用到
  • sources:多個源文件名
  • mappings:這是最重要的內容,表示了源代碼及編譯後代碼的關係,可是先略過這塊,下文中會詳細解釋

另外大部分應用都是由 webpack 來打包的,可能有些讀者會發現 webpack 的 source map 產出的字段於上面的略微有些不一致。

這是由於 webpack 魔改了一些東西,可是底下仍是基於這個庫實現的,只是變更了一些不涉及核心的字段,具體代碼

瀏覽器怎麼知道源文件和 source map 的關係?

這裏咱們以 webpack 作個實驗,經過 webpack5 對於如下代碼進行打包:

// index.js
const a = 1
console.log(a);
複製代碼

當咱們開啓 source map 選項之後,產物應該爲兩個文件,分別爲 bundle.js 以及 bundle.js.map

查看 bundle.js 文件之後咱們會發現代碼中存在這一一段註釋:

console.log(1);
//# sourceMappingURL=bundle.js.map
複製代碼

sourceMappingURL 就是標記了該文件的 source map 地址。

固然除此以外還有別的方式,經過查閱 MDN 文檔 發現還能夠經過 response header 的 SourceMap: <url> 字段來代表。

source map 是如何對應到源代碼的?

這是 source map 最核心的功能,也是最涉及知識盲區的一塊內容。

你們應該還記得上文中沒介紹的 mapping 字段吧,接下來咱們就來詳細瞭解這個字段的用處。

咱們仍是以剛纔打包的文件爲例,來看看產出的 source map 長啥樣(去掉了可有可無的):

{
  sources:["webpack://webpack-source-demo/./src/index.js"],
  names: ['console', 'log'],
  mappings: 'AACAA,QAAQC,IADE',
}
複製代碼

首先 mappings 的內容實際上是 Base64 VLQ 的編碼表示。

內容由三部分組成,分別爲:

  • 英文,表示源碼及壓縮代碼的位置關聯
  • 逗號,分隔一行代碼中的內容。好比說 console.log(a) 就由 consoleloga 三部分組成,因此存在兩個逗號。
  • 分號,表明換行

逗號和分號想必你們沒啥疑問,可是對於這幾個英文內容應該會很困惑。

其實這就是一種壓縮數字內容的編碼方式,畢竟源代碼可能很龐大,用數字表示行數及列數的話 source map 文件將也會很龐大,所以選用 Base 64 來表明數字用以減小文件體積。

好比說 A 表明了數字 0,C 表明了數字 1 等等,有興趣的讀者能夠經過該網站瞭解映射關係。

瞭解了這層編碼的映射關係,咱們再來聊聊這一串串英文到底表明了什麼。

其實這每串英文中的字母都表明了一個位置:

  1. 壓縮代碼的第幾列
  2. 哪一個源代碼文件,畢竟能夠多個文件打包成一個,對應 sources 字段
  3. 源代碼第幾行
  4. 源代碼第幾列
  5. names 字段裏的索引

這時讀者可能有個疑惑,爲啥沒有壓縮代碼的第幾行表示?這是由於壓縮後的代碼就一行,因此只須要表示第幾列就好了。


更新:有讀者詢問 Base64 表達的數字是有上限的,若是須要表示的數字很大的話該怎麼辦。實際上除了每一個分號中的第一串英文是用來表示代碼的第幾行第幾列的絕對位置以外,後面的都是相對於以前的位置來作加減法的。


瞭解完以上知識之後,咱們就來根據上文的內容解析下 AACAA 的具體含義吧,經過該網站咱們能夠知道 AACAA 對應了 [0,0,1,0,0],這裏須要注意的是數字都從 0 開始,筆者表述的時候會自動加一,畢竟代碼第零行聽起來怪怪的。

  1. 壓縮代碼的第一列
  2. 第一個源代碼文件,也就是 index.js 文件了
  3. 源代碼第二行了
  4. 源代碼的第一列
  5. names 數組中的第一個索引,也就是 console

經過以上的解析,咱們就能知道 console 在源代碼及壓縮文件中的具體位置了。

可是爲何 source map 會知道編譯後的代碼具體在什麼位置呢?這裏就要用到 AST 了。讓咱們打開網站輸入 console.log(a) 後觀察右邊的內容,你應該會發現如圖所示的數據:

image-20210516214636867

由於 source map 是由 AST 產出的,因此咱們能用上 AST 中的這個數據。

source map 的應用

通常來講 source map 的應用都是在監控系統中,開發者構建完應用後,經過插件將源代碼及 source map 上傳至平臺中。一旦客戶端上報錯誤後,咱們就能夠經過該庫來還原源代碼的報錯位置(具體 API 看文檔便可),方便開發者快速定位線上問題。

最後

source map 是咱們平常中常常用到的東西,可是直到學習這塊內容的時候才知道竟然涉及到了那麼多的知識盲區。

你們若是有什麼疑問歡迎在評論區交流。

相關文章
相關標籤/搜索