06.webpack中source-map的配置


認識source-map

一般情況下真實運行在瀏覽器上的代碼是經過webpack等前端構建工具打包之后的代碼,在打包的過程中會對代碼做壓縮和混淆丑化,所以這就會導致運行在瀏覽器的代碼和我們在開發階段寫的源代碼其實是有差異的,主要體現在以下幾個方面:

  1. 源碼中ES6+的語法會通過babel工具轉化為ES5語法;
  2. 源碼中的代碼行號以及列號經過編譯打包之后肯定會和瀏覽器端的不一致;
  3. 源碼中的變量名稱、函數名等經過壓縮丑化之后會被簡寫和替換;
  4. 源碼中如果采用了TS開發,那么還會轉化為JS代碼在瀏覽器中執行

以上這些問題其實最后都會反映到一個問題上面:那就是代碼在瀏覽器端運行的時候出錯的時候,我們在瀏覽器的devtools中debug調試錯誤的時候是及其困難的的,因為沒有任何一個人可以保證自己開發的代碼不會出錯!

那么我們如何在瀏覽器中調試這種打包轉化后不一致的代碼呢?答案就是source-map。source-map是將已經打包編譯后的代碼映射到原始的源文件的一個方案,配置source-map之后,就可以在瀏覽器中調試代碼的時候精確的為我們定位到源文件中代碼出錯的位置。

使用source-map

使用source-map的前提是瀏覽器必須支持source-map文件,也就是瀏覽器可以讀取並解析bundle.js.map這種文件,使用source-map需要兩個步驟:

  1. 配置webpack打包選項devtool來生成source-map
module.exports = {
	devtool:"source-map",
}
  1. 注釋
    上述配置代表最終打包后生成的js文件不僅會有一個bundle.js文件,還會有一個bundle.js.map文件,並且在bundle.js文件的最底端會有一行注釋:
//# sourceMappingURL=bundle.js.map

這行代碼告訴瀏覽器在加載bundle.js的文件的時候,要根據sourceMappingURL指向的文件路徑來下載該文件對應的bundle.js.map文件,然后瀏覽器就可以基於bundle.js.map文件中的信息加上bundle.js中的代碼還原出打包前的源代碼,這就是為什么在瀏覽器中調試的時候可以精確的定位到源代碼中錯誤信息,原因就是配置了source-map並且生成的bundle.js.map文件為瀏覽器提供了還原源代碼文件的必要信息。

在瀏覽器中查看還原后的源文件

在配置了source-map之后,如果我們要在瀏覽器中查看打包前的源文件,還需要在瀏覽器的devtools-設置-Sources選項中打開:Enable JavaScript source maps。

對比配置source-map前后瀏覽器devtools中sources選項的差異:

  1. 未配置devtool:source-map
    127.0.0.1:8848本地服務器端保存了一個bundle.js文件
    未配置devtool:source-map

  2. 配置devtool:source-map
    配置devtool:source-map
    瀏覽器除了127.0.0.1:8848本地服務器保存了打包之后的文件之外,還有一個文件夾保存了當前bundle.js中對應的所有源代碼文件,除了項目自身的還有用到webpack源碼中的文件都會被還原出來。

分析生成的source-map文件

最初source-map生成的文件是原始文件大小的10倍,后來經過兩個版本的優化現在已經可以做到只有原始文件大小的2.5倍左右,所以一個133kb的源文件最終的source-map文件大小應該在300kd左右。

source-map文件中以對象鍵值對的方式存放着如何通過打包之后的代碼映射源文件的一系列信息,具體有下面7個屬性:

{ 
	/* 當前使用的source-map版本 */
	"version": 3, 
	
	/* 瀏覽器當前加載的打包之后的文件 */
	"file": "js/bundle.js",
	
	/* 
	source-map用來和源文件進行映射的信息,由一串base64 VLQ(Veribale length quantity)可變長度值編碼組成,基於此信息確定代碼的位置信息
	 */
	"mappings": ";;;;;;;;;AAAA;AACA;AACA;....",
	
	/* 當前運行在瀏覽器中的代碼是由哪些源文件轉化過來的,數組中每一項都對應一個源文件路徑 */
	"sources": [
	    "webpack://webpack-demo/./src/js/CommonJS.js",
	    "webpack://webpack-demo/./src/js/ESModule.js",
		"webpack://webpack-demo/webpack/bootstrap",
	],
	
	/* 打包前的源代碼信息,將源代碼轉化為字符串和sources對應 */
	"sourcesContent": [
	  "function CommonSum (a,b) {\r\n\treturn a+b;\r\n}\r\n\r\nfunction CommonMul (a,b){\r\n\treturn a*b;\r\n}\r\n\r\nconsole.log(foo);\r\n\r\nmodule.exports = {\r\n\tCommonSum,\r\n\tCommonMul\r\n}",
	  "function ESModuleSum (a,b) {\r\n\treturn a+b;\r\n}\r\n\r\nfunction ESModuleMul (a,b){\r\n\treturn a*b;\r\n}\r\n\r\nconst ESModulec = 100;\r\n\r\n/* 基於ES Module語法 */\r\nexport default {\r\n\tESModuleSum,\r\n\tESModuleMul,\r\n\tESModulec\r\n}\r\n\r\n",
	],
	
	/* 如果是production模式,轉化后的變量等名稱會被混淆 names就可以還原出打包前的源代碼中的變量名稱;如果是development模式那么這里會是一個空數組
	 */
  "names": [
		"console",
        "log",
        "foo",
        "module",
        "exports",
        "CommonSum",
        "a",
        "b",
        "CommonMul",
	],
	
	/* 所有的sources中聲明的源文件路徑相對的根目錄 */
  "sourceRoot": ""
}

基於webpack的devtool選項配置不同的source-map

webpack配置中的devtool配置用來控制是否生成以及如何生成最終的source-map,webpack5的官方文檔中關於devtool的值總共有26個之多,不同的值會導致生成的source-map內容有所差異,並且對於webpack打包過程中的性能也有所差異,所以要基於不同的環境和不同的項目需求來靈活的配置devtool從而控制生成最佳的source-map。

1. 設置不生成source-map

1. 布爾值:false

將devtool配置為false代表不使用source-map,最終打包的文件中沒有source-map相關內容。注意這是一個布爾值,不要寫成devtool:"false",這樣會導致打包失敗。建議采用這個值來設置項目打包時不生成source-map。

2. production模式下devtool的默認值:"none"

如果mode設置為production生產模式,那么none是devtool的默認值,也就是說只要當前模式為生產模式那么默認是不會生成source-map的。

  • 注意1:如果mode設置為development開發模式,那么不可以將devtool的值設置為none,因為這個值只有在生產模式中才可以使用。

  • 注意2:如果mode設置為production生產模式,那么不用設置任何devtool選項就默認是none,此時去顯式的設置反而會報錯!!!

所以下面兩種設置方法都會報錯:

module.exports = {
	mode:"production",
	devtool:"none",
}

module.exports = {
	mode:"development",
	devtool:"none",
}

正確的設置方法是只聲明mode為production,讓默認配置生效即可。

module.exports = {
	mode:"production", // 生產環境下默認就是不生產source-map的
}

3. development模式下devtool的默認值:"eval"

如果當前的mode被設置為development開發模式,那么默認devtool的值是字符串eval,此時執行打包也不會生成source-map,雖然設置為eval不會生成source-map文件,但是webpack在對模塊打包的時候想對比於配置了source-map會做以下事情:

  • 將源代碼中模塊要導出的接口轉化為代碼字符串,放在eval()函數中執行。因為eval函數可以將參數字符串當做js代碼進行執行。

  • 在eval執行的代碼字符串的最后面添加一行注釋://# sourceURL=webpack://webpack-demo/./src/js/CommonJS.js?瀏覽器在解析到這行注釋的時候,會在對應的調試面板中生成當前bundle.js中對應的源代碼中的文件目錄,方便我們在開發模式下調試代碼。

  • eval模式下的打包構建速度是很快的,一般在開發模式下設置devtool為eval即可。

/* 配置devtool:source-map打包之后的__webpack_modules__對象 */
	var __webpack_modules__ = {
	 "./src/js/CommonJS.js":
		 function(module) {

			function CommonSum (a,b) {
				return a+b;
			}
			function CommonMul (a,b){
				return a*b;
			}
			console.log(foo);

			module.exports = {
				CommonSum,
				CommonMul
			}
		}
	}
	
/* 配置devtool:eval打包之后的__webpack_modules__對象 */
	var __webpack_modules__ = {
	"./src/js/CommonJS.js":
		function(module) {
			/* 將原本的導出的函數和變量轉化為字符串放在eval函數中執行;並且將eval函數中的代碼字符串轉譯並映射到sourceURL指定的文件中,簡單的還原源代碼
			 */
			eval(
				"function CommonSum (a,b) {\r\n\treturn a+b;\r\n}\r\n\r\nfunction CommonMul (a,b){\r\n\treturn a*b;\r\n}\r\n\r\nconsole.log(foo);\r\n\r\nmodule.exports = {\r\n\tCommonSum,\r\n\tCommonMul\r\n}\n\n//# sourceURL=webpack://webpack-demo/./src/js/CommonJS.js?");
		},
	}

2. 設置生成source-map

設置devtool的值為"source-map"就可以讓最終打包的文件中生成單獨的source-map文件,在上面介紹過這里不再贅述。

3. eval-source-map

設置devtool的值為"eval-source-map"可以生成source-map,但是生成的source-map不是單獨的文件,而是以DataUrl的形式添加到eval函數的最后面。

關鍵是//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxl...\n//這一行注釋,瀏覽器在加載bundle.js的時候就會執行eval函數,並且基於sourceMappingURL指向的base64資源加載source-map。

// 打包生成的bundle.js文件
var __webpack_modules__ = {
	"./src/js/CommonJS.js":
		function(module) {
			/* 將原本的導出的函數和變量轉化為字符串放在eval函數中執行;並且將eval函數中的代碼字符串轉譯並映射到sourceURL指定的文件中,簡單的還原源代碼
			 */
			eval("function CommonSum (a,b) {\r\n\treturn a+b;\r\n}\r\n\r\nfunction CommonMul (a,b){\r\n\treturn a*b;\r\n}\r\n\r\nconsole.log(foo);\r\n\r\nmodule.exports = {\r\n\tCommonSum,\r\n\tCommonMul\r\n}//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxl...\n//# sourceURL=webpack-internal:///./src/js/CommonJS.js\n");
		},
	}

4. inline-source-map

設置devtool的值為"inline-source-map"也可以生成source-map,也不會有單獨的.map文件,而是將source-map以DataUrl的形式添加到bundle.js文件的最后面。

// bundle.js文件

//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoianMvYnVuZGxlLmpzIiwibWFwcGluZ3MiOiI7Ozs7Ozs7OztBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Ozs7Ozs7Ozs7OztBQ2JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwrREFBZTtBQUNmO0FBQ0E7QUFDQTtBQUNBLENBQUM7QUFDRDs7Oz

5. cheap-source-map

設置devtool的值為"cheap-source-map"會生成單獨的source-map文件,並且想對比於source-map會更加高效一些,因為cheap在這里本來就是低開銷的意思,之所以高效的原因是因為cheap-source-map模式下生成的source-map文件中沒有列映射(Column Mapping),意思就是如果有代碼報錯,只能定位到報錯的行數,但是定位不到報錯的那一行的具體列數,但是一般在開發中只要定位到行即可。

  • 使用development模式下eval默認配置定位錯誤到列
    定位錯誤到列

  • 使用cheap-source-map定位錯誤定位到行
    定位錯誤到行

6. cheap-module-source-map

設置devtool的值為"cheap-module-source-map"會生成單獨的source-map文件,具體的行為類似於cheap-source-map,唯一不同的地方在於對源於loader處理過的源文件,其生成的source-map會更加優秀接近源代碼。

如果下面的ES6+語法代碼經過babel-laoder處理為低版本語法,那么cheap-source-map生成的source-map在定位錯誤的時候其實變量名稱和行號都是和源文件有差別的,這對於調試錯誤來說不是很友好。

  • 真實的源文件:使用了const和箭頭函數等ES6語法並且配置了bable-loader
const CommonSum = (a,b)=>{
return a+b;
}
const CommonMul = (a,b)=>{
	return a*b;
}
console.log(foo);
module.exports = {
	CommonSum,
	CommonMul
}
  • 使用cheap-source-map生成的source-map最終還原出來的源文件如下:可以看出由於es6+語法經過babel-loader的處理,已經將源代碼轉化為可以適配低版本瀏覽器的ES5語法,此時在定位錯誤的時候就會出現和真實的源文件中代碼行號不一致的問題。
var CommonSum = function CommonSum(a, b) {
  return a + b;
};

var CommonMul = function CommonMul(a, b) {
  return a * b;
};

console.log(foo);
module.exports = {
  CommonSum: CommonSum,
  CommonMul: CommonMul
};

以上這個問題就可以通過配置devtool的值為cheap-module-source-map來解決,比如源文件中的js代碼經過ts-loader、babel-loader的處理之后已經發生了變化,在打包前配置為cheap-module-source-map,就可以保證生成的source-map映射還原的源文件和真實的源文件一致,不會有行號、變量名前加下划線的差異,這其實也就是cheap-module-source-map和cheap-source-map兩種配置不同的地方,那就是對loader處理的文件有着更加好的處理。

  • 使用cheap-module-source-map生成的source-map最終還原出來的源文件如下:可以看出和真實源文件一模一樣,無任何差別,並且打包性能還比較高。
const CommonSum = (a,b)=>{
	return a+b;
}

const CommonMul = (a,b)=>{
	return a*b;
}

console.log(foo);

module.exports = {
	CommonSum,
	CommonMul
}

7. hidden-source-map

設置devtool的值為"hidden-source-map"會生成單獨的source-map文件,但是在budnle.js中的最底端不會有對source-map文件的引用注釋,相當於刪除了打包文件中對source-map文件引用的注釋。
hidden-source-map和false的區別在於是否生成了單獨的source-map文件,hidden-source-map雖然會將bundle.js中的引用注釋進行刪除,但是如果我們手動添加該注釋,那么source-map又會生效且映射出源文件。

/* 在bundle.js中添加如下注釋 */
//# sourceMappingURL=bundle.js.map

8. nosources-source-map值

設置devtool的值為"nosources-source-map"會生成單獨的source-map文件,但是生成的source-map只可以用來提示錯誤,並不會映射出源代碼文件,所以也就無法定位到具體的錯誤信息,也無法查看源碼。
點擊錯誤信息跳轉到source頁面時報錯:
nosources-source-map不映射源代碼

devtool選項配置時不同值的組合

webpack為devtool選項提供了26個值用來配置是否生成以及如何生成source-map,除了單獨定義以外還可以組合使用,但是應該按照如下的規則進行組合:

  1. inline-|hidden-|eval- 可選值;這三個值出現必須出現在第一位,三個值中選一個,不能重復選擇
  2. nosources- 可選值;如果沒有上面的三個值,那么這個值是第一位;如果有那么是第二位出現的
  3. cheap- 可選值;后面可以跟上module,也可以不用module
  4. source-map 固定組合,出現在末尾

綜合起來它們的組合規則如下所示:

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

source-map的最佳實踐

開發環境

推薦使用source-map或者cheap-module-source-map
可以快速幫助定位錯誤,調試代碼。

  • Vue腳手架中配置在開發環境就是source-map,生產環境是none缺省值
  • React腳手架中配置是做了一個判斷:
devtool:{
	/* 判斷是否為生產環境 */
	isEnvProduction?
		/* 如果是那么判斷是否應用source-map? */
		shouldUseSourceMap?'source-map':false
		/* 如果不是生產環境,那么判斷是否為開發環境並設置 */
	   :isEnvDevelopment && 'cheap-module-source-map'
}

測試環境

推薦使用source-map或者cheap-module-source-map

生產環境

推薦使用false或者缺省值也就是不寫
避免出現源代碼泄漏的風險


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM