简单来讲sourceMap就是一个映射关系,把错误在打包后代码中的位置和错误在源码中位置一一对应起来。在日常项目开发过程中,我们可以通过sourceMap定位到源代码。
sourceMap作用:当打包代码出错时,如果不使用sourceMap,我们只能知道错误在打包后的代码中的位置。使用sourceMap后,能直接定位到源代码中的出错位置,更方便代码查错和修改。
需要注意:一般只在开发环境下开启sourceMap,线上环境会关闭。上线问题的排查,可以通过将sourceMap上传到错误监控系统来实现。
认识Source Map
在开发过程中会经常使用新语言开发项目,最后会将源码转换成能在浏览器中直接运行的JavaScript代码。这样做虽能提升开发效率,但在调试代码的过程中我们会发现生成的代码可读性非常差,这给代码调试带来了不便。
sourceMap配置
eval
打包速度最快,性能最好。
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _helloworld__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);\n\ndocument.write(Object(_helloworld__WEBPACK_IMPORTED_MODULE_0__[\"helloworld\"])());\n\n//# sourceURL=webpack:///./src/index/index.js?");
打包后代码通过eval
包裹,并通过sourceURL指定代码对应的源码文件。
source-map
会生成单独的source map文件。
//# sourceMappingURL=index_3b645021.js.map
在打包后的文件的最后一行,通过sourceMappingURL指定当前代码对应的source map文件。sourceMap是一个映射关系,把打包后的文件的报错信息对应到源代码中对应的文件的行。
inline-source-map
不会生成单独的sourceMap文件,会将sourceMap文件中的内容以base64形式的字符串内联到打包后的文件里。
inline-source-map会映射错误的行和列信息。
cheap-inline-source-map
cheap的作用:
- 只映射行,不会映射列,这就是其与inline-source-map的区别所在。行列都映射的话比较浪费性能。
- 只对业务代码起作用,不会映射第三方模块中的报错信息。
cheap-module-inline-source-map
module:添加第三方模块中的报错信息的映射,不仅仅是业务代码。
Webpack
支持为转换生成的代码输出对应的Source Map
文件,以方便在浏览器中能通过源码调试。控制Source Map
输出的Webpack
配置项是devtool
,它有很多选项,如下表所示:
devtool | 含义 |
---|---|
空 | 不生成 Source Map |
eval | 每个 module 会封装到 eval 里包裹起来执行,并且会在每个 eval 语句的末尾追加注释 //# sourceURL=webpack:///./main.js |
source-map | 会额外生成一个单独 Source Map 文件,并且会在 JavaScript 文件末尾追加 //# sourceMappingURL=bundle.js.map |
hidden-source-map | 和 source-map 类似,但不会在 JavaScript 文件末尾追加 //# sourceMappingURL=bundle.js.map |
inline-source-map | 和 source-map 类似,但不会额外生成一个单独 Source Map 文件,而是把 Source Map 转换成 base64 编码内嵌到 JavaScript 中 |
eval-source-map | 和 eval 类似,但会把每个模块的 Source Map 转换成 base64 编码内嵌到 eval 语句的末尾,例如 //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW... |
cheap-source-map | 和 source-map 类似,但生成的 Source Map 文件中没有列信息,因此生成速度更快 |
cheap-module-source-map | 和 cheap-source-map 类似,但会包含 Loader 生成的 Source Map |
sourceMap关键字
其实以上表格只是列举了devtool
可能取值的一部分,它的取值其实可以由 source-map、eval、inline、hidden、cheap、module
这六个关键字随意组合而成。这六个关键字每个都代表一种特性,它们的含义分别是:
- eval:使用eval语句包裹模块代码;
- source-map:生成独立的以
.map
结尾的Source Map文件; - inline:将生成的Source Map转换成base64格式内嵌在打包后的JS文件中,不生成单独的
.map
文件; - cheap:生成的Source Map中不包含列信息,这样计算量更小,输出的Source Map文件更小;同时Loader输出的Source Map不会被采用;
- module:包含loader的Source Map;
- hidden:不在打包后的JS文件中指出Source Map文件所在,这样浏览器就不会自动加载Source Map;
该如何选择
如果不关心细节和性能,只是想在不出任何差错的情况下调试源码,可以直接设置成source-map
,但这样会造成两个问题:
- 在
source-map
模式下会输出质量最高最详细的Source Map
,这会造成构建速度缓慢,特别是在开发过程需要频繁修改的时会增加等待时间; source-map
模式下会把Source Map
暴露出去,如果构建发布到线上的代码的Source Map
暴露,就等同于源码被泄露;
为了解决以上两个问题,可以这样做:
- 开发环境最佳实践:
cheap-module-eval-source-map
。生成速度相对比较快,而且错误信息提示比较全。由于在开发环境下不会进行代码压缩,所以在Source Map中即使没有列信息,也不会影响断点调试; - 生产环境最佳实践:
cheap-module-source-map
。在生产环境下,把devtool设置成hidden-source-map
,意思是生成最详细的Source Map
,但不会将Source Map
暴露出去。由于在生产环境下会做代码压缩,一个JavaScript文件只有一行,所以需要列信息。
在生产环境下,通常不会将Source Map
上传到HTTP服务器让用户获取(防止项目源代码泄露),而是上传到JavaScript错误收集系统,在错误收集系统上根据Source Map
和收集到的JavaScript
运行错误堆栈,并计算出错误所在源码的位置。
注意:不要在生产环境下使用inline模式的Source Map
,因为这会使JavaScript文件变得很大,而且会泄露源码。
加载现有的Source Map
有些从npm安装的第三方模块是采用ES6或者TypeScript
编写的,它们在发布时会同时带上编译出来的JavaScript
文件和对应的Source Map
文件,以方便在使用它们出问题的时候调试它们;
默认情况下,Webpack
是不会去加载这些附加的Source Map
文件的,Webpack
只会在转换的过程中生成Source Map
。为了让Webpack
加载这些附加的Source Map
文件,需要安装source-map-loader
。 使用方法如下:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
// 只加载我们关心的目录下的Source Map,以提升构建速度
include: [path.resolve(root, 'node_modules/some-components/')],
use: ['source-map-loader'],
// 要把 source-map-loader 的执行顺序放到最前面,如果在 source-map-loader 之前有 Loader 转换了该 JavaScript 文件,会导致 Source Map 映射错误
enforce: 'pre'
}
]
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
由于source-map-loader
在加载Source Map
时计算量很大,因此要避免让该 Loader
处理过多的文件,不然会导致构建速度缓慢。通常会采用include
去命中只关心的文件。
再安装新引入的依赖:
yarn add source-map-loader -D
重启Webpack后,就能在浏览器中调试node_modules/some-components/
目录下的源码了。