sourcemap与debugger

475 阅读3分钟

^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$

前置知识:

  1. sourceURL: 当前代码在source中的路径
  2. sourceMappingURL:当前代码映射到源码的sourceMap内容

debugger原理:

  1. 源码打了一个断点,然后通过 CDP 调试协议,将断点位置发送给浏览器
  2. 浏览器运行的文件是打包后的bundle.js,通过sourcemap找到断点位置

所以debugger是一通过sourcemap找文件的过程

源码如下:

// src/index.js
function add(a, b) {
  return a + b
}
function multiple(a, b) {
  return a * b
}
var firstOp = 9
var secondOp = 10
add(firstOp, secondOp)

eval


eval("function add(a, b) {\n  return a + b\n}\nfunction multiple(a, b) {\n  return a * b\n}\nvar firstOp = 9\nvar secondOp = 10\nadd(firstOp, secondOp)\n\n//# sourceURL=webpack://webpack-demo/./src/index.js?");

eval在浏览器source中新建一个文件,文件地址就是webpack://webpack-demo/./src/index.js

source-map

// bundle.js
/******/ (() => { // webpackBootstrap
var __webpack_exports__ = {};
/*!**********************!*\
  !*** ./src/index.js ***!
  \**********************/
function add(a, b) {
  return a + b
}
function multiple(a, b) {
  return a * b
}
var firstOp = 9
var secondOp = 10
add(firstOp, secondOp)
/******/ })()
;
//# sourceMappingURL=bundle.js.map
// bundle.js.map
{"version":3,"file":"bundle.js","mappings":";;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sB","sources":["webpack://webpack-demo/./src/index.js"],"sourcesContent":["function add(a, b) {\n  return a + b\n}\nfunction multiple(a, b) {\n  return a * b\n}\nvar firstOp = 9\nvar secondOp = 10\nadd(firstOp, secondOp)"],"names":[],"sourceRoot":""}

sourceMappingURL指向的sourcemap包含行和列,还有源码sourcesContent,内容比较大。sourcesContent存储源码是为了当找不到源码文件的时候还能进行断点和报错,如果源码保证能找到的话,可以不需要sourcesContent字段

eval-source-map

/*
 * 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__ = ({

/***/ "./src/index.js":
/*!**********************!*\
  !*** ./src/index.js ***!
  \**********************/
/***/ (() => {

eval("function add(a, b) {\n  return a + b\n}\nfunction multiple(a, b) {\n  return a * b\n}\nvar firstOp = 9\nvar secondOp = 10\nadd(firstOp, secondOp)//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9zcmMvaW5kZXguanMuanMiLCJtYXBwaW5ncyI6IkFBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vd2VicGFjay1kZW1vLy4vc3JjL2luZGV4LmpzP2I2MzUiXSwic291cmNlc0NvbnRlbnQiOlsiZnVuY3Rpb24gYWRkKGEsIGIpIHtcbiAgcmV0dXJuIGEgKyBiXG59XG5mdW5jdGlvbiBtdWx0aXBsZShhLCBiKSB7XG4gIHJldHVybiBhICogYlxufVxudmFyIGZpcnN0T3AgPSA5XG52YXIgc2Vjb25kT3AgPSAxMFxuYWRkKGZpcnN0T3AsIHNlY29uZE9wKSJdLCJuYW1lcyI6W10sInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///./src/index.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__["./src/index.js"]();
/******/ 	
/******/ })()
;

eval会产生两个sourceURL,一个是[module],第二个是具体的地址,后面的会覆盖前面的(不是很懂为什么要有sourceURL=[module])

eval-source-map有关的debugger需要特别注意

// 这是上面的sourceMappingURL解码
{  
    "version":3,  
    "file":"./src/index.js.js",  
    "mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA",  
    "sources":**[  
        "webpack://webpack-demo/./src/index.js?b635"  
    ],  
    "sourcesContent":**[  
        "function add(a, b) {\n  return a + b\n}\nfunction multiple(a, b) {\n  return a * b\n}\nvar firstOp = 9\nvar secondOp = 10\nadd(firstOp, secondOp)"  
    ],  
    "names":**[  
  
    ],  
    "sourceRoot":""  
}

sources里面的路径会带hash值,造成本地找不到源码,所以浏览器打不了断点,除非手动debugger

inline

// bundle.js
/******/ (() => { // webpackBootstrap
var __webpack_exports__ = {};
/*!**********************!*\
  !*** ./src/index.js ***!
  \**********************/
function add(a, b) {
  return a + b
}
function multiple(a, b) {
  return a * b
}
var firstOp = 9
var secondOp = 10
add(firstOp, secondOp)
/******/ })()
;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYnVuZGxlLmpzIiwibWFwcGluZ3MiOiI7Ozs7O0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCIiwic291cmNlcyI6WyJ3ZWJwYWNrOi8vd2VicGFjay1kZW1vLy4vc3JjL2luZGV4LmpzIl0sInNvdXJjZXNDb250ZW50IjpbImZ1bmN0aW9uIGFkZChhLCBiKSB7XG4gIHJldHVybiBhICsgYlxufVxuZnVuY3Rpb24gbXVsdGlwbGUoYSwgYikge1xuICByZXR1cm4gYSAqIGJcbn1cbnZhciBmaXJzdE9wID0gOVxudmFyIHNlY29uZE9wID0gMTBcbmFkZChmaXJzdE9wLCBzZWNvbmRPcCkiXSwibmFtZXMiOltdLCJzb3VyY2VSb290IjoiIn0=

就是把sourcemap以base64编码直接写在打包文件的最后

hidden

// bundle.js
/******/ (() => { // webpackBootstrap
var __webpack_exports__ = {};
/*!**********************!*\
  !*** ./src/index.js ***!
  \**********************/
function add(a, b) {
  return a + b
}
function multiple(a, b) {
  return a * b
}
var firstOp = 9
var secondOp = 10
add(firstOp, secondOp)
/******/ })()
;

打包文件没有sourcemap链接,但还是生成了sourcemap文件

nosources

{"version":3,"file":"bundle.js","mappings":";;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,sB","sources":["webpack://webpack-demo/./src/index.js"],"names":[],"sourceRoot":""}

去掉了sourcesContent,体积精简了很多,需要确保能根据sourcemap找到源文件

cheap

{"version":3,"file":"bundle.js","mappings":";;;;;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","sources":["webpack://webpack-demo/./src/index.js"],"sourcesContent":["function add(a, b) {\n  return a + b\n}\nfunction multiple(a, b) {\n  return a * b\n}\nvar firstOp = 9\nvar secondOp = 10\nadd(firstOp, secondOp)"],"names":[],"sourceRoot":""}

sourcemap信息减少到行(mappings长度有所减少)

cheap打断点的时候也需要注意,它发送的断点路径非常简略,会打到别的位置上,若是本地调试,不推荐用cheap

GIF 2023-2-10 15-30-51.gif

module

源码通过loader会改动,module就是从源码经过每个loader都会做一层映射,所以能找到源码,不然就只能找到打包前那一个状态的源码

debugger实战:

vue-cli

eval-cheap-module-source-map

断点位置:vue-demo1/src/App.vue

webpack需要一层映射,将debugger的地方加一个webpack://发送给浏览器

"sourceMapPathOverrides": {
        "meteor://💻app/*": "${workspaceFolder}/*",
        "webpack:///./~/*": "${workspaceFolder}/node_modules/*",
        "webpack://?:*/*": "${workspaceFolder}/*"
      }

所以浏览器接收到的断点位置是:webpack://vue-demo1/src/App.vue

__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */   "render": function() { return /* binding */ render; }
/* harmony export */ });
/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! vue */ "./node_modules/vue/dist/vue.runtime.esm-bundler.js");
/* harmony import */ var _assets_logo_png__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./assets/logo.png */ "./src/assets/logo.png");


const _hoisted_1 = /*#__PURE__*/(0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementVNode)("img", {
  alt: "Vue logo",
  src: _assets_logo_png__WEBPACK_IMPORTED_MODULE_1__
}, null, -1 /* HOISTED */);

function render(_ctx, _cache, $props, $setup, $data, $options) {
  const _component_HelloWorld = (0,vue__WEBPACK_IMPORTED_MODULE_0__.resolveComponent)("HelloWorld");
  return (0,vue__WEBPACK_IMPORTED_MODULE_0__.openBlock)(), (0,vue__WEBPACK_IMPORTED_MODULE_0__.createElementBlock)(vue__WEBPACK_IMPORTED_MODULE_0__.Fragment, null, [_hoisted_1, (0,vue__WEBPACK_IMPORTED_MODULE_0__.createVNode)(_component_HelloWorld, {
    msg: "Welcome to Your Vue.js App"
  })], 64 /* STABLE_FRAGMENT */);
}//# sourceURL=[module]
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiLi9ub2RlX21vZHVsZXMvYmFiZWwtbG9hZGVyL2xpYi9pbmRleC5qcz8/Y2xvbmVkUnVsZVNldC00MC51c2VbMF0hLi9ub2RlX21vZHVsZXMvdnVlLWxvYWRlci9kaXN0L3RlbXBsYXRlTG9hZGVyLmpzPz9ydWxlU2V0WzFdLnJ1bGVzWzNdIS4vbm9kZV9tb2R1bGVzL3Z1ZS1sb2FkZXIvZGlzdC9pbmRleC5qcz8/cnVsZVNldFswXS51c2VbMF0hLi9zcmMvQXBwLnZ1ZT92dWUmdHlwZT10ZW1wbGF0ZSZpZD03YmE1YmQ5MC5qcyIsIm1hcHBpbmdzIjoiOzs7Ozs7O0FBQzZDO2dDQUEzQ0MsdURBQUFBLENBQTRDO0VBQXZDQyxHQUFHLEVBQUMsVUFBVTtFQUFDQyxHQUF1QixFQUF2QkgsNkNBQUFBOzs7OztxS0FBcEJJLFVBQTRDLEVBQzVDQyxnREFBQUEsQ0FBOENDO0lBQWxDQyxHQUFHLEVBQUM7RUFBNEIiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly92dWUtZGVtbzEvLi9zcmMvQXBwLnZ1ZT85MWEwIl0sInNvdXJjZXNDb250ZW50IjpbIjx0ZW1wbGF0ZT5cbiAgPGltZyBhbHQ9XCJWdWUgbG9nb1wiIHNyYz1cIi4vYXNzZXRzL2xvZ28ucG5nXCI+XG4gIDxIZWxsb1dvcmxkIG1zZz1cIldlbGNvbWUgdG8gWW91ciBWdWUuanMgQXBwXCIvPlxuPC90ZW1wbGF0ZT5cblxuPHNjcmlwdD5cbmltcG9ydCBIZWxsb1dvcmxkIGZyb20gJy4vY29tcG9uZW50cy9IZWxsb1dvcmxkLnZ1ZSdcblxuZXhwb3J0IGRlZmF1bHQge1xuICBuYW1lOiAnQXBwJyxcbiAgY29tcG9uZW50czoge1xuICAgIEhlbGxvV29ybGRcbiAgfSxcbiAgbW91bnRlZCgpIHtcbiAgICBkZWJ1Z2dlclxuICB9XG59XG48L3NjcmlwdD5cblxuPHN0eWxlPlxuI2FwcCB7XG4gIGZvbnQtZmFtaWx5OiBBdmVuaXIsIEhlbHZldGljYSwgQXJpYWwsIHNhbnMtc2VyaWY7XG4gIC13ZWJraXQtZm9udC1zbW9vdGhpbmc6IGFudGlhbGlhc2VkO1xuICAtbW96LW9zeC1mb250LXNtb290aGluZzogZ3JheXNjYWxlO1xuICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gIGNvbG9yOiAjMmMzZTUwO1xuICBtYXJnaW4tdG9wOiA2MHB4O1xufVxuPC9zdHlsZT5cbiJdLCJuYW1lcyI6WyJfaW1wb3J0c18wIiwiX2NyZWF0ZUVsZW1lbnRWTm9kZSIsImFsdCIsInNyYyIsIl9ob2lzdGVkXzEiLCJfY3JlYXRlVk5vZGUiLCJfY29tcG9uZW50X0hlbGxvV29ybGQiLCJtc2ciXSwic291cmNlUm9vdCI6IiJ9
//# sourceURL=webpack-internal:///./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/App.vue?vue&type=template&id=7ba5bd90

sourceMappingURL解析

**{  
    "version":3,  
    "file":"./node_modules/babel-loader/lib/index.js??clonedRuleSet-40.use[0]!./node_modules/vue-loader/dist/templateLoader.js??ruleSet[1].rules[3]!./node_modules/vue-loader/dist/index.js??ruleSet[0].use[0]!./src/App.vue?vue&type=template&id=7ba5bd90.js",  
    "mappings":";;;;;;;AAC6C;gCAA3CC,uDAAAA,CAA4C;EAAvCC,GAAG,EAAC,UAAU;EAACC,GAAuB,EAAvBH,6CAAAA;;;;;qKAApBI,UAA4C,EAC5CC,gDAAAA,CAA8CC;IAAlCC,GAAG,EAAC;EAA4B",  
    "sources":**[  
        "webpack://vue-demo1/./src/App.vue?91a0"  
    ],  
    "sourcesContent":**[  
        "<template>\n  <img alt=\"Vue logo\" src=\"./assets/logo.png\">\n  <HelloWorld msg=\"Welcome to Your Vue.js App\"/>\n</template>\n\n<script>\nimport HelloWorld from './components/HelloWorld.vue'\n\nexport default {\n  name: 'App',\n  components: {\n    HelloWorld\n  },\n  mounted() {\n    debugger\n  }\n}\n</script>\n\n<style>\n#app {\n  font-family: Avenir, Helvetica, Arial, sans-serif;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  text-align: center;\n  color: #2c3e50;\n  margin-top: 60px;\n}\n</style>\n"  
    ],  
    "names":**[  
        "_imports_0",  
        "_createElementVNode",  
        "alt",  
        "src",  
        "_hoisted_1",  
        "_createVNode",  
        "_component_HelloWorld",  
        "msg"  
    ],  
    "sourceRoot":""  
}

由于传递的断点位置和sourcemap的不匹配(有hash值),所以断点不生效,若是想要断点生效,需要改变sourcemap的模式(不用eval)

vite

vite都是绝对地址,在源码打了断点后,断点发送给浏览器,但是vite有多个热更新文件,断点可能会打在同路径的热更新文件上

断点位置: vue-demo2\src\components\HelloWorld.vue

相对路径: src\components\HelloWorld

例如

http://localhost:5173/src/components/HelloWorld.vue?vue&type=style&index=0&scoped=e17ea971&lang.css

http://localhost:5173/src/components/HelloWorld.vue

这两个文件都收到了断点,但是他们不是源码

image.png

**{  
    "version":3,  
    "names":**[  
  
    ],  
    "sources":**[  
        "C:/Users/mjgao/vue-demo2/src/components/HelloWorld.vue"  
    ],  
    "sourcesContent":**[  
        "<script setup>\nimport { reactive, ref } from 'vue'\nlet a = ref(1)\ndefineProps({\n  msg: {\n    type: String,\n    required: true,\n  },\n})\n\nconst me = reactive({\n  name: 'gmj',\n})\n</script>\n\n<template>\n  <div class=\"greetings\">\n    <h1 class=\"green\">{{ msg }}</h1>\n    <h3>\n      You’ve successfully created a project with\n      <a href=\"https://vitejs.dev/\" target=\"_blank\" rel=\"noopener\">Vite</a> +\n      <a href=\"https://vuejs.org/\" target=\"_blank\" rel=\"noopener\">Vue 3</a>.\n    </h3>\n  </div>\n</template>\n\n<style scoped>\nh1 {\n  font-weight: 500;\n  font-size: 2.6rem;\n  top: -10px;\n}\n\nh3 {\n  font-size: 1.2rem;\n}\n\n.greetings h1,\n.greetings h3 {\n  text-align: center;\n}\n\n@media (min-width: 1024px) {\n  .greetings h1,\n  .greetings h3 {\n    text-align: left;\n  }\n}\n</style>\n"  
    ],  
    "mappings":"AACA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;;;;;;;;;;;;AADrB;AAEd,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAMZ;AACF;AACA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACd,CAAC,CAAC;;;;;;;;;;;qBAIK,KAAK,EAAC,WAAW;qBAChB,KAAK,EAAC,OAAO;iEACjB,oBAIK;gCAJD,8CAEF;eAAA,oBAAqE;IAAlE,IAAI,EAAC,qBAAqB;IAAC,MAAM,EAAC,QAAQ;IAAC,GAAG,EAAC,UAAU;KAAC,MAAI;gCAAI,KACrE;eAAA,oBAAqE;IAAlE,IAAI,EAAC,oBAAoB;IAAC,MAAM,EAAC,QAAQ;IAAC,GAAG,EAAC,UAAU;KAAC,OAAK;gCAAI,IACvE;;;;wBANF,oBAOM,OAPN,UAOM;IANJ,oBAAgC,MAAhC,UAAgC,mBAAX,UAAG;IACxB,UAIK"  
}

如果把webRoot改写成这样"webRoot": "${workspaceFolder}/aaa",

这时候相对路径就是aaa\src\components\HelloWorld,但是绝对路径没有变,所以能够忽略掉热更新文件以及没有采用绝对路径的文件,例如main.js打的断点就没有用了

image.png

截屏2023-02-08 00.06.07.png

这里mac和win的系统还是有点区别