前言:

我们在使用webpack压缩我们的代码后,如果我们压缩后的代码报错了,我们该如何快速的定位我们的错误的源代码所在处,这时候我们就需要source-map了。

1.什么是source-map?

如果我们的打包后,没有source-map,那么我们的代码报错,我们查看是,只能如下图所示,查到我们打包后文件的所在位置,而不能定位到我们源代码错误的位置。
image.png
image.png
Source Map, 顾名思义,是保存源代码映射关系的文件。上面提到的,我们找不到报错的文件的相关信息,那有没有一个拥有源文件与打包后文件的映射关系的文件,让它来告诉我们呢?这个文件就是 Source Map 文件

2.source-map文件

当我们在webpack使用最基本的 devtool:’source-map’ 时,会生成一个 map的文件,那个就是我们的source-map文件。

  1. console.log("hello world");
  1. {
  2. "version": 3,
  3. "file": "main.js",
  4. "mappings": ";;;;;AAAA",
  5. "sources": [
  6. "webpack://01-source-map/./app.js"
  7. ],
  8. "sourcesContent": [
  9. "console.log(\"hello world\");\r\n"
  10. ],
  11. "names": [],
  12. "sourceRoot": ""
  13. }
  • version:Source map 的版本,目前为 3。
  • file:转换后的文件名。
  • mappings:记录位置信息的字符串。这个的话,用到了 VLQ 编码相关,详细看JavaScript Source Map 详解
  • sources:转换前的文件。该项是一个数组,表示可能存在多个文件合并。
  • names:转换前的所有变量名和属性名。
  • sourceRoot:转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空。

3.Webpack中如何使用source-map

我们先来看看 Webpack 中的 devtool 配置
官方文档列出了很多种组合,在这之前,我们可以先好好看看以下的关键字,不管是什么组合都是下面的一个或者多个拼接而成的

  • source map。产生 .map 文件(配合 eval 或者 inline 使用的时候,会不生成 source map 文件,具体要看哪个模式)

eval, hidden, inline这三个模式是互斥的,它们不能同时出现

  • eval:使用 eval 包裹块代码,不单独生成一个 .map 文件
  • inline:将 .map 作为 DataURI 嵌入,不单独生成一个 .map 文件
  • hidden:会单独生成一个.map文件,但是不会自动关联源码,不会生成DataURI

下面三个模式可以叠加:

  • nosources:使用这个关键字的Source Map不包含sourcesContent,调试时只能看到文件信息和行信息,无法看到源码。
  • cheap:不生成列信息
  • module:包含 loader 的 source map
  1. class Person {
  2. constructor() {
  3. this.name = "123"
  4. }
  5. print() {
  6. console.log(this.name);
  7. }
  8. }
  9. let p = new Person()
  10. p.print()

(1) source-map

这应该是最基本的模式,打包后的map文件

  1. {
  2. "version": 3,
  3. "file": "main.js",
  4. "mappings": ";;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mB",
  5. "sources": [
  6. "webpack://01-source-map/./app.js"
  7. ],
  8. "sourcesContent": [
  9. "class Person {\r\n constructor() {\r\n this.name = \"123\"\r\n }\r\n print() {\r\n console.log(this.name);\r\n }\r\n}\r\nlet p = new Person()\r\np.print()\r\n"
  10. ],
  11. "names": [],
  12. "sourceRoot": ""
  13. }

(2) eval-source-map

打包出来只有 main.js。eval-source-map —— 每个模块使用 eval() 执行,并且 source map 转换为 DataUrl 后添加到 eval() 中。

  1. /*
  2. * ATTENTION: An "eval-source-map" devtool has been used.
  3. * This devtool is neither made for production nor for readable output files.
  4. * It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.
  5. * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
  6. * or disable the default devtool with "devtool: false".
  7. * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
  8. */
  9. /******/ (() => { // webpackBootstrap
  10. /******/ var __webpack_modules__ = ({
  11. /***/ "./app.js":
  12. /*!****************!*\
  13. !*** ./app.js ***!
  14. \****************/
  15. /***/ (() => {
  16. eval("class Person {\r\n constructor() {\r\n this.name = \"123\"\r\n }\r\n print() {\r\n console.log(this.name);\r\n }\r\n}\r\nlet p = new Person()\r\np.print()\r\n//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9hcHAuanMuanMiLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8wMS1zb3VyY2UtbWFwLy4vYXBwLmpzPzlhNzgiXSwic291cmNlc0NvbnRlbnQiOlsiY2xhc3MgUGVyc29uIHtcclxuICBjb25zdHJ1Y3RvcigpIHtcclxuICAgIHRoaXMubmFtZSA9IFwiMTIzXCJcclxuICB9XHJcbiAgcHJpbnQoKSB7XHJcbiAgICBjb25zb2xlLmxvZyh0aGlzLm5hbWUpO1xyXG4gIH1cclxufVxyXG5sZXQgcCA9IG5ldyBQZXJzb24oKVxyXG5wLnByaW50KClcclxuIl0sIm5hbWVzIjpbXSwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///./app.js\n");
  17. /***/ })
  18. /******/ });
  19. /************************************************************************/
  20. /******/
  21. /******/ // startup
  22. /******/ // Load entry module and return exports
  23. /******/ // This entry module can't be inlined because the eval-source-map devtool is used.
  24. /******/ var __webpack_exports__ = {};
  25. /******/ __webpack_modules__["./app.js"]();
  26. /******/
  27. /******/ })()
  28. ;

(3) inline-source-map

我们看到实际上只打包出来 main.bundle.js 文件,没有 source map ,这个时候实际上 Souce Map 实际上是内嵌到我们的 main.bundle.js 中了
Source Map内容通过base64生成 sourceMappingURL放在js文件中引入。

  1. /******/ (() => { // webpackBootstrap
  2. var __webpack_exports__ = {};
  3. /*!****************!*\
  4. !*** ./app.js ***!
  5. \****************/
  6. class Person {
  7. constructor() {
  8. this.name = "123"
  9. }
  10. print() {
  11. console.log(this.name);
  12. }
  13. }
  14. let p = new Person()
  15. p.print()
  16. /******/ })()
  17. ;
  18. // 看这,source map 内嵌到了这
  19. //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsIm1hcHBpbmdzIjoiOzs7OztBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vMDEtc291cmNlLW1hcC8uL2FwcC5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJjbGFzcyBQZXJzb24ge1xyXG4gIGNvbnN0cnVjdG9yKCkge1xyXG4gICAgdGhpcy5uYW1lID0gXCIxMjNcIlxyXG4gIH1cclxuICBwcmludCgpIHtcclxuICAgIGNvbnNvbGUubG9nKHRoaXMubmFtZSk7XHJcbiAgfVxyXG59XHJcbmxldCBwID0gbmV3IFBlcnNvbigpXHJcbnAucHJpbnQoKVxyXG4iXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=

(4) hidden-source-map

代码中没有sourceMappingURL,浏览器不自动引入Source Map。

  1. /******/ (() => { // webpackBootstrap
  2. var __webpack_exports__ = {};
  3. /*!****************!*\
  4. !*** ./app.js ***!
  5. \****************/
  6. class Person {
  7. constructor() {
  8. this.name = "123"
  9. }
  10. print() {
  11. console.log(this.name);
  12. }
  13. }
  14. let p = new Person()
  15. p.print()
  16. /******/ })()
  17. ;

(5)nosources-source-map

可以看到错误在哪行哪列,但是看不到源码(点进去是看不到源码的)
image.png
image.png

(6) cheap-source-map

我们发现 mapping 部分不一样,主要是因为 cheap 不生成列信息,所以会少一些。我们测试的代码比较少,所以看起来区别不大,但如果代码量很大的时候,实际上会差别挺大的。具体的表现的话,跟上面有点差不多,就是点进去详情的时候,光标不会自动跳到具体某一列。

  1. {
  2. "version": 3,
  3. "file": "main.js",
  4. "mappings": ";;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA",
  5. "sources": [
  6. "webpack://01-source-map/./app.js"
  7. ],
  8. "sourcesContent": [
  9. "class Person {\r\n constructor() {\r\n this.name = \"123\"\r\n }\r\n print() {\r\n console.log(this.name);\r\n }\r\n}\r\nlet p = new Person()\r\np.print()\r\n"
  10. ],
  11. "names": [],
  12. "sourceRoot": ""
  13. }

(7) cheap-moudle-source-map

生成一个没有列信息(column-mappings)的 SourceMaps 文件,**)

  1. {
  2. "version": 3,
  3. "file": "main.js",
  4. "mappings": ";;;;;;;;;;;AAAA;AACA;AAAA;;AACA;AACA;;;;AACA;AACA;AACA;;;;;;AAEA;AACA;AACA",
  5. "sources": [
  6. "webpack://01-source-map/./app.js"
  7. ],
  8. "sourcesContent": [
  9. "class Person {\r\n constructor() {\r\n this.name = \"123\"\r\n }\r\n print() {\r\n console.log(this.name);\r\n }\r\n}\r\nconsole.log(a);\r\nlet p = new Person()\r\np.print()\r\n"
  10. ],
  11. "names": [],
  12. "sourceRoot": ""
  13. }

4.开发环境和生产环境

对于开发环境,通常希望更快速的 source map 但是对于生产环境,出于安全性考量,我们甚至不需要sourcemap,如果

(1) 开发环境

(1)eval 的执行效率高 (2)”cheap (低开销)”,因为它没有生成列映射 (column mapping),只是映射行数源自 loader 的 source map 会得到更好的处理结果

推荐使用:

  • eval
  • eval-source-map
  • eval-cheap-source-map
  • eval-cheap-module-source-map | devtool | 源码级别 | 构建速度 | 列信息 | | —- | —- | —- | —- | | eval | webpack + loader处理后的代码 | 快 | + | | eval-source-map | 源码 | 慢 | + | | eval-cheap-source-map | loader处理后的代码 | 中 | - | | eval-cheap-module-source-map | 源码 | 中 | - |

(2) 生产环境

在生产环境,我们比较在意的是安全性,所以下面从源码级别、安全性和列信息来对比。

devtool 源码级别 安全性 列信息
none - - -
source-map 源码 浏览器会加载source-map,调试时会暴露源码 +
hidden-source-map 源码 会生成map文件,但浏览器不会加载source-map。可以将map文件与错误上报工具结合使用 +
nosources-source-map 源码堆栈 没有sourcesContent,调试只能看到模块信息和行信息,不能看到源码 -