简单来讲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?");
1

打包后代码通过eval包裹,并通过sourceURL指定代码对应的源码文件。

source-map

会生成单独的source map文件。

//# sourceMappingURL=index_3b645021.js.map
1

在打包后的文件的最后一行,通过sourceMappingURL指定当前代码对应的source map文件。sourceMap是一个映射关系,把打包后的文件的报错信息对应到源代码中对应的文件的行。

inline-source-map

不会生成单独的sourceMap文件,会将sourceMap文件中的内容以base64形式的字符串内联到打包后的文件里。

inline-source-map会映射错误的行和列信息。

cheap-inline-source-map

cheap的作用:

  1. 只映射行,不会映射列,这就是其与inline-source-map的区别所在。行列都映射的话比较浪费性能。
  2. 只对业务代码起作用,不会映射第三方模块中的报错信息。

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'
      }
    ]
  }
};
1
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
1

重启Webpack后,就能在浏览器中调试node_modules/some-components/目录下的源码了。