Webpack中sourcemap的配置
sourcemap是為了解決開發代碼與實際運行代碼不一致時幫助我們debug到原始開發代碼的技術。尤其是如今前端開發中大部分的代碼都經過編譯,打包等工程化轉換。比如開發環境下用scss寫樣式, 想在瀏覽器中在線編輯css那樣編輯scss就不是那么容易了。從我自己看過的資料中, sourcemap的概念最早出現在12年, jquer1.9是較早支持sourcemap的庫。這篇博客比較有代表性:Introduction to JavaScript Source Maps,阮一峰的文章JavaScript Source Map 詳解也大量參考該博客。關於sourcemap的原理及作用,基本在這兩篇文章中講清楚了。回到webpack中的sourcemap,就我這幾天的琢磨, 這方面資料相對比較零散,但凡搜索Webpack中sourcemap的配置, 總是能得到千篇一律的如下信息:
Sourcemap type Quality Notes
eval: 生成代碼 每個模塊都被eval執行,並且存在@sourceURL
cheap-eval-source-map: 轉換代碼(行內) 每個模塊被eval執行,並且sourcemap作為eval的一個dataurl
cheap-module-eval-source-map: 原始代碼(只有行內) 同樣道理,但是更高的質量和更低的性能
eval-source-map: 原始代碼 同樣道理,但是最高的質量和最低的性能
cheap-source-map: 轉換代碼(行內) 生成的sourcemap沒有列映射,從loaders生成的sourcemap沒有被使用
cheap-module-source-map: 原始代碼(只有行內) 與上面一樣除了每行特點的從loader中進行映射
source-map: 原始代碼 最好的sourcemap質量有完整的結果,但是會很慢
webpack中devtool的配置的官方文檔在這 :webpack-devtool
疑問
反正我看完這些說明是雲里霧里, 就我自己而言, 有3個疑問:
-
eval和sourcemap有什么關系,eval模式是sourcemap嗎?
-
包含cheap關鍵字的配置中只有行內是什么意思?
-
這幾種不同的配置有什么區別?
解答
看似配置項很多, 其實只是五個關鍵字eval
,source-map
,cheap
,module
,inline
的任意組合。這五個關鍵字每一項都代表一個特性, 這四種特性可以任意組合。它們分別代表以下五種特性(單獨看特性說明有點不明所以,別急,往下看):
-
eval: 使用eval包裹模塊代碼
-
source-map: 產生
.map
文件 -
cheap: 不包含列信息(關於列信息的解釋下面會有詳細介紹)也不包含loader的sourcemap
-
module: 包含loader的sourcemap(比如jsx to js ,babel的sourcemap)
-
inline: 將
.map
作為DataURI嵌入,不單獨生成.map
文件(這個配置項比較少見)
了解了以上各種不同特性, 再來逐一解答以上問題。
eval和sourcemap有什么關系,eval模式是sourcemap嗎?
eval
和source-map
都是webpack中devtool的配置選項, eval
模式是使用eval
將webpack中每個模塊包裹,然后在模塊末尾添加模塊來源//# souceURL
, 依靠souceURL
找到原始代碼的位置。包含eval關鍵字的配置項並不單獨產生.map
文件(eval模式有點特殊, 它和其他模式不一樣的地方是它依靠sourceURL來定位原始代碼, 而其他所有選項都使用.map
文件的方式來定位)。包含source-map
關鍵字的配置項都會產生一個.map
文件,該文件保存有原始代碼與運行代碼的映射關系, 瀏覽器可以通過它找到原始代碼的位置。(注:包含inline
關鍵字的配置項也會產生.map
文件,但是這個map文件是經過base64編碼作為DataURI嵌入),舉個栗子:eval-source-map
是eval
和source-map
的組合,可知使用eavl
語句包括模塊,也產生了.map
文件。webpack將.map
文件作為DataURI替換eval
模式中末尾的//# souceURL
。按照我自己的理解, eval
和.map
文件都是sourcemap實現的不同方式,雖然大部分sourcemap的實現是通過產生.map
文件, 但並不表示只能通過.map
文件實現。下面是eval模式后產生的模塊代碼:
包含cheap關鍵字的配置中只有行內是什么意思?
這里的列信息指的是代碼的不包含原始代碼的列信息。 官方文檔對於包含cheap
的解釋是這樣的:
> cheap-source-map - A SourceMap without **column-mappings**. SourceMaps > from loaders are not used.
這句話翻譯過來就是“在cheap-source-map模式下sourcemap不包含列信息,也不包含loaders的sourcemap”這里的“column-mappings”就是代碼列數的意思,是否包含loaders的sourcemap有什么區別將在之后提到。debug的時候大部分人都只在意代碼的行數, 很少關注列數, 列數就是該行代碼從第一個字符開始到定位字符的位置(包括空白字符)包含cheap
關鍵字的模式不包含列信息,體現在webpack中就是:如果包含cheap
關鍵字,則產生的.map
文件不包含列信息。也就是說當你在瀏覽器中點擊該代碼的位置時, 光標只定位到行數,不定位到具體字符位置。而不包含cheap
關鍵字時, 點擊控制台log將會定位到字符位置。
包含列信息后點擊原始代碼的定位,注意光標位置:
不包含列信息的光標位置:
這篇博客:Go to a line number at a specific column直觀地展示了列數的概念。如果深入到webpack中的細節中體會該配置項,可以看這篇博客:SurviveJS:Source Maps ,該文章對比了webpack中所有配置項中.map
文件的代碼,這里截取eval-source-map
和cheap-source-map
的模式產生的.map
文件代碼中的mappings
字段對比:
devtool: 'eval-source-map'
"mappings": "AAAAA,QAAQC,GAAR,CAAY,aAAZ",
devtool: 'cheap-source-map'
"mappings": "AAAA",
注:這里使用了VLQ編碼,(關於VLQ編碼還可參考這里:前端構建:Source Maps詳解) 在VLQ編碼中,逗號,
表示字符列分割,分號;
表示行分割。包含cheap
關鍵字的配置項不包含列信息,也就沒有逗號。關於VLQ編碼, 本文最初的阮一峰的文章中有所解釋。而不包含loader的sourcemap指的是不包含loader的sourcemap,不包含它時候如果你使用了諸如babel等代碼編譯工具時, 定位到的原始代碼將是經過編譯后的代碼位置,而非原始代碼。
比如當我用babel編譯JS的時候,如果包含不包含loaders的sourcemap,此時debug到的將是編譯后的代碼, 而非原始代碼,如圖(這是使用cheap-source-map模式未包含loaders的sourcemap情況下的截圖, debug的位置與之前的對比截圖是同一個地方):
這幾種不同的配置有什么區別?
通過以上兩個問題的解釋, webpack中的sourcemap各個配置項異同應該有了一定認識,乍看之下各個配置項很難記憶, 但其實從每個關鍵字所代表的特性入手, 就能體會到他們的異同。他們在webpack中的主要區別一個體現在重構的性能上, 總的來說eval
性能最好,source-map
性能最低,但就我自身的實踐來看大多用的是最完整的source-map
,該模式對於不管是js還是css,scss等都能很好的覆蓋, 相反其他模式都不完整, 在開發環境下重構性能似乎比不上功能的完善。
另外需要補充的是module
關鍵字, 當加上module
關鍵字webpack將會添加loader的sourcemap。