微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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或者缺省值也就是不写
避免出现源代码泄漏的风险

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐