前言:
我们在使用webpack压缩我们的代码后,如果我们压缩后的代码报错了,我们该如何快速的定位我们的错误的源代码所在处,这时候我们就需要source-map了。
1.什么是source-map?
如果我们的打包后,没有source-map,那么我们的代码报错,我们查看是,只能如下图所示,查到我们打包后文件的所在位置,而不能定位到我们源代码错误的位置。

Source Map, 顾名思义,是保存源代码映射关系的文件。上面提到的,我们找不到报错的文件的相关信息,那有没有一个拥有源文件与打包后文件的映射关系的文件,让它来告诉我们呢?这个文件就是 Source Map 文件
2.source-map文件
当我们在webpack使用最基本的 devtool:’source-map’ 时,会生成一个 map的文件,那个就是我们的source-map文件。
console.log("hello world");
{"version": 3,"file": "main.js","mappings": ";;;;;AAAA","sources": ["webpack://01-source-map/./app.js"],"sourcesContent": ["console.log(\"hello world\");\r\n"],"names": [],"sourceRoot": ""}
- 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
class Person {constructor() {this.name = "123"}print() {console.log(this.name);}}let p = new Person()p.print()
(1) source-map
这应该是最基本的模式,打包后的map文件
{"version": 3,"file": "main.js","mappings": ";;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mB","sources": ["webpack://01-source-map/./app.js"],"sourcesContent": ["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"],"names": [],"sourceRoot": ""}
(2) eval-source-map
打包出来只有 main.js。eval-source-map —— 每个模块使用 eval() 执行,并且 source map 转换为 DataUrl 后添加到 eval() 中。
/** ATTENTION: An "eval-source-map" devtool has been used.* This devtool is neither made for production nor for readable output files.* It uses "eval()" calls to create a separate source file with attached SourceMaps in the browser devtools.* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)* or disable the default devtool with "devtool: false".* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).*//******/ (() => { // webpackBootstrap/******/ var __webpack_modules__ = ({/***/ "./app.js":/*!****************!*\!*** ./app.js ***!\****************//***/ (() => {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");/***/ })/******/ });/************************************************************************//******//******/ // startup/******/ // Load entry module and return exports/******/ // This entry module can't be inlined because the eval-source-map devtool is used./******/ var __webpack_exports__ = {};/******/ __webpack_modules__["./app.js"]();/******//******/ })();
(3) inline-source-map
我们看到实际上只打包出来 main.bundle.js 文件,没有 source map ,这个时候实际上 Souce Map 实际上是内嵌到我们的 main.bundle.js 中了
Source Map内容通过base64生成 sourceMappingURL放在js文件中引入。
/******/ (() => { // webpackBootstrapvar __webpack_exports__ = {};/*!****************!*\!*** ./app.js ***!\****************/class Person {constructor() {this.name = "123"}print() {console.log(this.name);}}let p = new Person()p.print()/******/ })();// 看这,source map 内嵌到了这//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5qcyIsIm1hcHBpbmdzIjoiOzs7OztBQUFBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vMDEtc291cmNlLW1hcC8uL2FwcC5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJjbGFzcyBQZXJzb24ge1xyXG4gIGNvbnN0cnVjdG9yKCkge1xyXG4gICAgdGhpcy5uYW1lID0gXCIxMjNcIlxyXG4gIH1cclxuICBwcmludCgpIHtcclxuICAgIGNvbnNvbGUubG9nKHRoaXMubmFtZSk7XHJcbiAgfVxyXG59XHJcbmxldCBwID0gbmV3IFBlcnNvbigpXHJcbnAucHJpbnQoKVxyXG4iXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=
(4) hidden-source-map
代码中没有sourceMappingURL,浏览器不自动引入Source Map。
/******/ (() => { // webpackBootstrapvar __webpack_exports__ = {};/*!****************!*\!*** ./app.js ***!\****************/class Person {constructor() {this.name = "123"}print() {console.log(this.name);}}let p = new Person()p.print()/******/ })();
(5)nosources-source-map
可以看到错误在哪行哪列,但是看不到源码(点进去是看不到源码的)
(6) cheap-source-map
我们发现 mapping 部分不一样,主要是因为 cheap 不生成列信息,所以会少一些。我们测试的代码比较少,所以看起来区别不大,但如果代码量很大的时候,实际上会差别挺大的。具体的表现的话,跟上面有点差不多,就是点进去详情的时候,光标不会自动跳到具体某一列。
{"version": 3,"file": "main.js","mappings": ";;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sources": ["webpack://01-source-map/./app.js"],"sourcesContent": ["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"],"names": [],"sourceRoot": ""}
(7) cheap-moudle-source-map
生成一个没有列信息(column-mappings)的 SourceMaps 文件,**)
{"version": 3,"file": "main.js","mappings": ";;;;;;;;;;;AAAA;AACA;AAAA;;AACA;AACA;;;;AACA;AACA;AACA;;;;;;AAEA;AACA;AACA","sources": ["webpack://01-source-map/./app.js"],"sourcesContent": ["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"],"names": [],"sourceRoot": ""}
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,调试只能看到模块信息和行信息,不能看到源码 | - |
