首先根據谷歌開發者文檔的介紹,Source Map通常與下列類型的預處理器搭配使用:前端
爲何呢?由於一般咱們運行在瀏覽器中的代碼是通過處理的,處理後的代碼可能與開發時代碼相差很遠,這就致使開發調試和線上排錯變得困難。這時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就是目的之一。app
那麼它是什麼呢?它描述了咱們在調試時能看到的源碼內容。編輯器
下面來看看它的取值以及含義函數
quality | 含義 |
---|---|
bundled code | 模塊未分離 |
generated code | 模塊分離,未經loader處理的代碼 |
transformed code | 模塊分離,經loader處理過的代碼 |
original source | 你本身所寫的代碼 |
without source content | 生成的Source Map中不包含sourcesContent |
(lines only) | 包含行信息,不包含列信息 |
quality就決定了咱們調試時能看到的源碼內容,因此選取devtool的值時,須要根據項目實際狀況配合quality來選擇。而devtool有20+個可選值,咱們須要進一步來理解其組成原則。
特別提醒:指定devtool時,要與mode配合使用。
複製代碼
首先,上述模式中有三類關鍵詞:
俗話說,實踐是檢驗真理的惟一標準學懂同樣東西的最好方式,下面有一個🌰來實踐一下。
這裏是一個簡單的demo,裏面有連個文件,其中主文件main.js引入了一個a.js。爲了更好的模擬實際場景,還使用了webpack中還使用了babel-loader來處理js文件。
具體以下圖:
下面就來實操一波~
這幾個模式是互斥的,描述的是Source Map的引入方式。
Source Map內容經過base64放在js文件中引入。
代碼中沒有sourceMappingURL,瀏覽器不自動引入Source Map。
生成代碼和Source Map內容混淆在一塊兒,經過eval輸出。
使用這個關鍵字的Source Map不包含sourcesContent,調試時只能看到文件信息和行信息,沒法看到源碼。
這個關鍵字用於指定調試信息的完整性
不包含列信息,而且源碼是進過loader處理過的
這裏能夠看到,點擊對應文件時,會跳轉到對應文件,可是光標是在第一列(缺乏列信息則只定位到第一列),而且箭頭函數也被轉換成了function。
不包含列信息,源碼是開發時的代碼
也是隻能看到列信息,不過代碼是原汁原味的開發時所寫的代碼。
根據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 | 位置信息 |
這裏以chrome瀏覽器爲例(其餘瀏覽器應該也是相似的喔~)。
瀏覽器加載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文件後的結果。
直接舉個栗子🌰:
源碼: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 |
這個表格描述了每一個字符的處理先後的對應位置信息,有了這個表格就能夠作位置的映射了。
能夠看到對於幾個字符來講,須要存儲的信息就已經很龐大了,因此這種記錄方式還須要進行優化。
優化點以下:
;
來分割每行輸出代碼,使用
,
來分割每一個輸出代碼的位置信息。
優化後以下表:
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索引) 複製代碼
在上述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官網的devtool[2]推薦:
推薦使用:
在開發環境,咱們比較在乎的是開發體驗,因此下面從源碼級別、構建速度和列信息來對比。
devtool | 源碼級別 | 構建速度 | 列信息 |
---|---|---|---|
eval | webpack + loader處理後的代碼 | 快 | + |
eval-source-map | 源碼 | 慢 | + |
eval-cheap-source-map | loader處理後的代碼 | 中 | - |
eval-cheap-module-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
。
關注「漫步大前端」, 第一時間獲取優質文章。
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