一文完全搞懂webpack devtool

爲何須要Source Map

首先根據谷歌開發者文檔的介紹,Source Map通常與下列類型的預處理器搭配使用:前端

  • 轉譯器(Babel)
  • 編譯器(TypeScript)
  • Minifiers(UglifyJS)

爲何呢?由於一般咱們運行在瀏覽器中的代碼是通過處理的,處理後的代碼可能與開發時代碼相差很遠,這就致使開發調試和線上排錯變得困難。這時Source Map就登場了,有了它瀏覽器就能夠從轉換後的代碼直接定位到轉換前的代碼。在webpack中,能夠經過devtool選項來配置Source Map。webpack

配置項

瞭解了爲何須要Source Map,咱們來了瞭解下webpack能生成哪些類型的Source Map。web

從webpack官網上了解到,devtool的值有20+種chrome

聽起來很嚇人,不過幸虧它有固定的模式。瀏覽器

[inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map安全

經過關鍵詞的組合,就能夠生成用於各類場景的Source Map。babel

理解quality

在學習各類值以前,咱們須要先來了解一下表格中的quality列。配置某個屬性值,咱們是爲了達到某個目的,而quality就是目的之一。app

那麼它是什麼呢?它描述了咱們在調試時能看到的源碼內容編輯器

下面來看看它的取值以及含義函數

quality 含義
bundled code 模塊未分離
generated code 模塊分離,未經loader處理的代碼
transformed code 模塊分離,經loader處理過的代碼
original source 你本身所寫的代碼
without source content 生成的Source Map中不包含sourcesContent
(lines only) 包含行信息,不包含列信息

理解devtool

quality就決定了咱們調試時能看到的源碼內容,因此選取devtool的值時,須要根據項目實際狀況配合quality來選擇。而devtool有20+個可選值,咱們須要進一步來理解其組成原則。

特別提醒:指定devtool時,要與mode配合使用。
複製代碼

首先,上述模式中有三類關鍵詞:

  • inline、hidden、eval
  • nosources
  • cheap[module]

俗話說,實踐是檢驗真理的惟一標準學懂同樣東西的最好方式,下面有一個🌰來實踐一下。

這裏是一個簡單的demo,裏面有連個文件,其中主文件main.js引入了一個a.js。爲了更好的模擬實際場景,還使用了webpack中還使用了babel-loader來處理js文件。

具體以下圖:

下面就來實操一波~

inline、hidden、eval

這幾個模式是互斥的,描述的是Source Map的引入方式。

inline

Source Map內容經過base64放在js文件中引入。

hidden

代碼中沒有sourceMappingURL,瀏覽器不自動引入Source Map。

eval

生成代碼和Source Map內容混淆在一塊兒,經過eval輸出。

nosources

使用這個關鍵字的Source Map不包含sourcesContent,調試時只能看到文件信息和行信息,沒法看到源碼。

cheap[module]

這個關鍵字用於指定調試信息的完整性

cheap

不包含列信息,而且源碼是進過loader處理過的

這裏能夠看到,點擊對應文件時,會跳轉到對應文件,可是光標是在第一列(缺乏列信息則只定位到第一列),而且箭頭函數也被轉換成了function。

cheap-module

不包含列信息,源碼是開發時的代碼

也是隻能看到列信息,不過代碼是原汁原味的開發時所寫的代碼。

Source Map是如何工做的

Source Map規範

根據Source Map v3規範,推薦的格式是:

{
 "version" : 3,  "file": "out.js",  "sourceRoot": "",  "sources": ["foo.js", "bar.js"],  "sourcesContent": [null, null],  "names": ["src", "maps", "are", "fun"],  "mappings": "A,AAAB;;ABCDE;" } 複製代碼

下面來解釋一下每一個屬性表明的含義。

屬性 掘力值下限
version Source Map文件版本
file 該Source Map對應文件的名稱
sourceRoot 源文件根目錄,這個值會加在每一個源文件以前
sources 源文件列表,用於mappings
sourcesContent 源代碼字符串列表,用於調試時展現源文件,列表每一項對應於sources
names 源文件變量名和屬性名,用於mappdings
mappings 位置信息

瀏覽器與source-map

這裏以chrome瀏覽器爲例(其餘瀏覽器應該也是相似的喔~)。

加載source-map

瀏覽器加載source-map是經過js文件中的sourceMappingRUL來加載的,並且sourceMapping支持兩種形式:文件路徑或base64格式

加載source-map以後,在瀏覽器dev tool中的Sources tab就能看到對應的信息了。

這裏重點講解一下map文件中的sources字段和sourcesContent字段。

sources字段對應的是文件信息,會在瀏覽器的Sources中生成對應目錄結構。以後再將sourcesContent中的內容對應填入上述生成的文件中。咱們在調試時爲啥能看到文件信息和源碼內容,就是sources和sourcesContent共同做用的結果。

這裏經過一個栗子來看看,我在sources中添加了一個文件hello.js,而且在sourcesContent中對應添加了內容hello~。下面是瀏覽器加載map文件後的結果。

mappdings詳解

直接舉個栗子🌰:

源碼:input.js
i am handsome you are ugly 複製代碼
轉換後代碼:output.js
i am handsome you are ugly 複製代碼

咱們要如何記錄這位置對應信息呢?這裏能夠參考這篇文章[1]

位置信息必須包含的有:輸出位置(Output location)、源文件(Input)、源碼位置(Input location)、源碼。

因此根據上面的栗子,咱們能夠得出以下表格。

Output location Input Input location Character
L1,C0 index.js L1, C0 i
L1,C2 index.js L1, C2 a
L1,C3 index.js L1, C3 m
L1,C5 index.js L1, C5 h
L1,C6 index.js L1, C6 a
L1,C7 index.js L1, C7 n
L1,C8 index.js L1, C8 d
L1,C9 index.js L1, C9 s
L1,C10 index.js L1, C10 o
L1,C11 index.js L1, C11 m
L1,C12 index.js L1, C12 e
L1,C14 index.js L2, C0 y
L1,C15 index.js L2, C1 o
L1,C16 index.js L2, C2 u
L1,C18 index.js L2, C4 a
L1,C19 index.js L2, C5 r
L1,C20 index.js L2, C6 e
L2,C0 index.js L2, C8 u
L2,C1 index.js L2, C9 g
L2,C2 index.js L2, C10 l
L2,C3 index.js L2, C11 y

這個表格描述了每一個字符的處理先後的對應位置信息,有了這個表格就能夠作位置的映射了。

能夠看到對於幾個字符來講,須要存儲的信息就已經很龐大了,因此這種記錄方式還須要進行優化。

優化點以下:

  1. 能夠把 輸入文件放入一個列表中,這樣在位置信息中就可使用列表索引來表示了。
  2. 對於源碼, 沒有必要記錄每一個字符的對應信息,咱們只須要記錄變量、屬性名就能夠了(單詞),可使用一個列表來保存單詞,位置信息 只記錄單詞首個字符位置便可。
  3. 輸出文件的行信息是相對重複的,因此可使用 ;來分割每行輸出代碼,使用 ,來分割每一個輸出代碼的位置信息。
  4. 源碼的每一個單詞可使用對於上一個單詞的 相對位置,這些位置信息(數字)能夠更小些。

優化後以下表:

sources: [index.js]
names: [i, am, handsome, you, are, ugly] 複製代碼
Output location Input index Input location Name index
L1,C0 0 L1, C0 0
L1,C+2 +0 L1, C+2 +1
L1,C+3 +0 L1, C+3 +1
L1,C+9 +0 L2, C-5 +1
L1,C+4 +0 L2, C+4 +1
L2,C-18 +0 L2, C+4 +1

此時位置信息能夠記錄成

mappings: "0|0|1|0|0,2|0|1|2|1,3|0|1|3|1,9|0|2|-5|1,4|0|2|4|1;-18|0|2|4|1"
備註: 1. 數字使用分隔符「|」分割 2. 0|0|1|0|0表明 -> 0(輸出單詞列), 0(輸入文件sources索引),10(輸入單詞行列),0(單詞names索引) 複製代碼

使用VLQ進行優化

在上述mappings中,由於每個位置不必定是一個數字的,因此必須使用|分隔符來分割數字。若是能省略分隔符,mappings的大小能夠獲得很大的優化。這時就須要引入VLQ(Variable Length Quantities)了。

VLQ的特色就是能夠很是精簡地表示很大的數值。其原理是使用6個二進制位來表示字符,其中最高位表示是否連續(0:不連續,1:連續),最低位表示是正仍是負(0:正數,1:負數)。

舉兩個栗子🌰🌰:數字1與-23。

數字:1
二進制:1 VLQ編碼:000010 Base64 VLQ: C 複製代碼
數字:-23
絕對值:23 二進制:10111 VLQ編碼:101111 000001 Base64 VLQ: vB 複製代碼

這裏拿-23來分解一下生成過程。

第一步:去23的二進制碼 -> 10111
第二步:將10111分紅兩部分,第一部分是後四位,後面部分是五位爲一部分 -> 一、0111 -> 0000一、0111 第三步:按VLQ格式拼接 -> 101111 000001 其中,101111是 1【連續標識位】 + 0111 + 1【正負標識位】 000001是 0【連續標識位】+ 00001 第四步:對照Base64索引表(下表) 複製代碼

最後來看一下上述mappings優化以後長啥樣吧。

優化前:
mappings: "0|0|1|0|0,2|0|1|2|1,3|0|1|3|1,9|0|2|-5|1,4|0|2|4|1;-18|0|2|4|1" 優化後 mappings: "AACAA,EACEC,GACGC,SAELC,IAEIC,lBAEIC" 複製代碼

項目中如何使用

經過上面的理論。咱們對source-map以及webpack中devtool配置項已經有一些瞭解了,下面從實際出發,看看在項目不一樣環境中應該如何配置webpack。

webpack是如何推薦的

先直接來看看webpack官網的devtool[2]推薦:

Development

推薦使用:

  • eval
  • eval-source-map
  • eval-cheap-source-map
  • eval-cheap-module-source-map

在開發環境,咱們比較在乎的是開發體驗,因此下面從源碼級別、構建速度和列信息來對比。

devtool 源碼級別 構建速度 列信息
eval webpack + loader處理後的代碼 +
eval-source-map 源碼 +
eval-cheap-source-map loader處理後的代碼 -
eval-cheap-module-source-map 源碼 -

Production

推薦使用:

  • none
  • source-map
  • hidden-source-map
  • nosources-source-map

在生產環境,咱們比較在乎的是安全性,因此下面從源碼級別、安全性和列信息來對比。

devtool 源碼級別 安全性 列信息
none - - -
source-map 源碼 瀏覽器會加載source-map,調試時會暴露源碼 +
hidden-source-map 源碼 會生成map文件,但瀏覽器不會加載source-map。能夠將map文件與錯誤上報工具結合使用 +
nosources-source-map 源碼堆棧 沒有sourcesContent,調試只能看到模塊信息和行信息,不能看到源碼 -

實際項目是如何配置

翻了一下項目組中的項目,開發環境使用的是eval-cheap-module-source-map,而生產環境這邊多大數只須要知道報錯的模塊和行號就能夠了,因此使用的是nosources-source-map

最後


關注「漫步大前端」, 第一時間獲取優質文章。

參考資料

[1]

Source Maps under the hood: https://docs.microsoft.com/zh-cn/archive/blogs/davidni/source-maps-under-the-hood-vlq-base64-and-yoda#comment-626

[2]

webpack: https://user-gold-cdn.xitu.io/2020/6/28/172f8f3771f18da1

相關文章
相關標籤/搜索