Daotin

前端debug技巧

todo

VSCode Debugger 调试网页

VSCode Debugger 的配置

json
{
  "name": "vscode调试",

  // launch:launch 的意思是把 url 对应的网页跑起来,指定调试端口,然后页面会自动attach到这个端口。
  // attach:如果你已经有一个在调试模式跑的浏览器了,那直接 attach上就行
  "request": "launch",

  // 浏览器
  "type": "chrome",

  "runtimeArgs": [
    "--auto-open-devtools-for-tabs" // 打开网页都默认调起 Chrome DevTools
    "--incognito" // 无痕模式
  ],

  // "canary"、"stable"、"custom" 或浏览器可执行文件的路径。 Custom 表示自定义包装器、自定义生成或 CHROME_PATH 环境变量。
  "runtimeExecutable": "canary",

  // userDataDir 是保存用户数据的地方,比如你的浏览记录、cookies、插件、书签、网站的数据等等
  // 默认是 true,代表创建一个临时目录来保存用户数据。
  // 设置为false,可以记录登录状态、历史记录
  // 也可以指定一个自定义的路径,这样用户数据就会保存在那个目录下
  // 注意:userDataDir 只能跑一个 Chrome 实例,所以我使用chrome调试,edge用作其他搜索
  "userDataDir": false,

  "url": "http://localhost:8080",

  // workspaceFolder: 把 / 的 url 映射到了 ${workspaceFoder} 项目根目录
  "webRoot": "${workspaceFolder}",

  // 是否关闭sourcemap映射
  "sourceMaps": false,

  // 除了启动开发服务器然后连上 url 调试之外,也可以直接指定某个文件,VSCode Debugger 会启动静态服务器提供服务
  "file": "$workspaceFolder}/index.html"

  // sourcemap重映射:有时候,sourcemap 到的文件路径在本地里找不到,代码就是只读的,这时候就需要再次手动映射到本地文件
  // 以下是:默认有三个值,分别是把 meteor、webpack 开头的 path 映射到了本地的目录下。
  // 其中 ?:* 代表匹配任意字符,但不映射,而 * 是用于匹配字符并映射的。
  // 举例:最后一个 webpack://?:/ 到 ${workspaceFolder}/* 的映射,就是把 webpack:// 开头,后面接任意字符 + / 然后是任意字符的路径映射到了本地的项目目录
  "sourceMapPathOverrides": {
    "meteor://💻app/*": "${workspaceFolder}/*",
    "webpack:///./~/*": "${workspaceFolder}/node_modules/*",
    "webpack://?:*/*": "${workspaceFolder}/*"
  }
}

常用配置:

json
{
  "name": "vscode调试",
  "request": "launch",
  "type": "chrome",
  "runtimeArgs": ["--auto-open-devtools-for-tabs"],
  "runtimeExecutable": "canary",
  "userDataDir": false,
  "url": "http://localhost:8080",
  "webRoot": "${workspaceFolder}"
}

调试 Vue 项目

启动后,先打个断点发现不起效,分析是路径不对。

怎么找路径?

先在代码里面写 debugger,进入调试窗口,查看目前的状况。

  • 发现他从一个乱七八糟的路径,映射到了 webpack://vue-demo1/src/App.vue?11c4 的路径下。
  • 然后在 VSCode Debugger 里看看这个路径,发现是 /Users/guang/code/vue-demo1/src/App.vue?11c4
  • 其实这个路径已经做过了映射,就是完成了从 webpack:///vue-demo1/src/App.vue?11c4 到 /Users/guang/code/vue-demo1/src/App.vue?11c4 的映射。(使用的 sourceMapPathOverrides 的第三条配置)

但问题就出现在后面多了一个 ?hash 的字符串,导致路径不对了。所以在我们 vue 代码里面打断点会显示未关联,因为对应的本地文件没有。

那为什么会多这样一个 hash 呢?

这是因为 vue cli 默认的 devtool 设置是 eval-cheap-module-source-map,sourceURL 的路径是通过 [module] 指定的,而模块名后默认会带 ?hash。

解决办法就是:从 eval-cheap-module-source-map 变为 source-map。

  • 去掉 module(最后 webpack 的配置为:config.devtool = "source-map")
  • 添加配置(具体可以打断点在调试界面看路径)
    json
    "sourceMapPathOverrides": {
      "webpack:///src/*": "${workspaceFolder}/src/*"
    }

调试源码

调试 vue 源码

1、下载 vue 源码

plain text
git clone https://github.com/vuejs/core vue3-core

2、使用 pnpm 安装

PS:注意去掉 package.json 中的 puppeteer,用来做集成测试的,一是用不到,二是网络问题不去掉一直安装失败。
plain text
pnpm install

3、生成 sourcemap

  • 方式一:直接 pnpm run build --sourcemap 打包即可
  • 方式二:由于在 rollup.config.js 看到 output.sourcemap = !!process.env.SOURCE_MAP ,想要生成 sourcemap,需要 SOURCE_MAP 环境变量为 true。
  • 安装 cross-env
plain text
pnpm add cross-env
  • 设置 "build": "cross-env SOURCE_MAP=true node scripts/build.js"
  • 使用 pnpm run build打包

然后去 packages 下各个包下的 dist 产物,你就会发现有 sourcemap 了。

4、把 runtime-core 包下的 dist 复制出来,替换 vue 项目的 @vue/runtime-core 下的 对应的 dist 目录。

注意是替换而不是删了整个 dist,因为还有一个文件是需要的。

Photo
注意 1:vue 源码的版本一定要和 vue 项目中使用的 vue 版本相同,否则启动会报错!

注意 2:使用 vite 搭建的 vue 项目没有生效。原因:vite 没有 source-map-loader 这些东西,无法读取 node_modules 下模块的 sourcemap。解决办法:一是调试源码就用 webpack 的 vue3 项目。二是把 vite 中配置 optimizeDeps: { exclude: ["vue"], } 排除一下 deps,然后删除 node_modules 下 .Vite文件夹。

注意 3:如果无法打断点,就把 vue 项目重新安装跑一下。。。

Photo

按照流程跑起来后,确实链接的是本地路径,不过现在 sourcemap 到的路径不大对,没有 runtime-core 的包名,不能编辑:

解决方式有 2 种:

  1. 一种是配置下 sourceMapPathOverrides,把这段路径再做一次映射,映射到源码目录就可以了。
  2. 另一种方式就是改造下 vue3 的 build 脚本,让生成的 sourcemap 就直接是正确的路径。

但是第一种的话不太好找,所以采用第二种。

javascript
// output.sourcemap = !!process.env.SOURCE_MAP
output.sourcemapPathTransform = (relativeSourcePath, sourcemapPath) => {
  const newSourcePath = path.join(
    path.dirname(sourcemapPath),
    relativeSourcePath
  );
  return newSourcePath;
};

重新 build 一下,然后进行 dist 复制。

注意:对于 webpack 的项目,这里还需要清除一下 babel loader 的缓存,删除 node_modules/.cache/babel-loader 即可。

调试 vite 源码

TODO

调试 ElementUI 源码

一、首先定位 ElementUI 源码

1、可以通过事件断点的方式,慢慢进入组件的源码。

  • 但是组件的代码是被编译打包过的,不是最初的源码。
  • 为了调试最初的源码,需要下载了 Element UI 的代码,build 出了一份带有 sourcemap 的代码。覆盖项目 node_modules 下的代码,重新跑 dev server,这时候就可以直接调试组件源码了。

2、当有了 sourcemap 之后,Chrome DevTools 会直接把 vue 文件列在 sources 里,我们可以找到对应的 vue 文件来打断点,就不用通过事件断点来找了。

能够调试 Element UI 源码之后,想知道组件内部都有哪些逻辑的话,就可以直接在源码断点调试了。

VSCode Debugger 打的断点是如何在网页里生效的

比如源代码的路径是 /Users/xxx/vite-demo/src/App.vue ,最后打包后运行可能是http://xxx.com/bundle.js。

由于文件是关联了 sourcemap 的,所以就直接映射到了/Users/xxx/vite-demo/src/App.vue 文件,而 App.vue 中打的断点就是通过 CDP 调试协议进行通信的。

Photo

打断点的 7 种方式

  • 异常断点(Uncaught Exceptions):在抛异常处断住
  • 条件断点(conditional breakpoint):在满足某个表达式的时候断住
  • 日志点(logpoint):打印日志但不断住,觉得断住太多次的时候可以用这个
  • DOM 断点:在 DOM 子树修改、属性修改、节点删除的时候断住
    Photo
  • Event Listenter 断点:在某个事件发生的时候断住
  • url /image/2023 包含某内容的请求时断住
    Photo

  • sourcemap 的原理和作用
    • webpack 的 sourcemap 配置
    • 调试 React、Vue 源码
  • Node.js 的调试
    • VSCode Node Debugger 配置
    • 调试 Nest.js 源码
    • 命令行工具的两种调试
  • 调试下 ESLint、Babel、TypeScript 的源码
  • Chrome DevTools
    • 6 种打断点的方式
    • Performance :分析和优化网页性能
    • LightHouse :检测页面性能
    • Memory:分析内存问题
  • 调试移动端网页
    • 调试安卓和 ios 的网页
    • Charles 的使用
  • 调试工具的原理
    • 自定义调试工具的实现原理
  • 实现一个简易版 React DevTools
    • Chrome DevTools 的原理
  • 实现 mini puppeteer
  • 分别实现 Chrome DevTools 的各个部分的功能

VSCode Debugger 调试 Node.js

TODO

VSCode Node Debugger 配置

TODO

调试 npm scripts

TODO

调试 ESLint、Babel、TypeScript 的源码,Nest.js 源码。

TODO

Chrome DevTools

  • 学习 Chrome DevTools 的 7 种打断点的方式
  • 用 Performance 工具分析和优化网页性能
  • 用 LightHouse 工具检测页面性能
  • 用 Memory 工具分析内存问题
  • 用 Layers 工具进行图层分析等。

如何调试移动端网页

移动端网页调试离不开代理,所以我们还会学习 Charles 的使用。

调试工具的原理

Chrome DevTools 的实现原理

实现 mini puppeteer

Related posts