【前端】vue+vscode断点调试详解

15,460 阅读8分钟

前言

  • 非开箱即用,仅作好奇心满足、复杂项目中自定义断点配置、下个项目中不求人不百度立即断点调试
  • 涉及面较宽,需要一定经验,且各处均不会太深入(我也菜)
  • 如果你到处搜索但始终解决不了: vscode无法连接上页面、提示未验证断点(灰空心圆)、断点漂移乱飞到其他行、断点实际中断时所在位置不对、单步跳过执行时去到奇怪的位置,那么这里可能解决问题

js如何断点

如图↑,直接sources面板中打断点。

若在第6行的空行打断点,看到一个动画显示断点从第六行溜到第七行。vscode的debugger扩展实质就是与这个通信,并反馈结果到自己的ui上。

因此如果你看到vscode中用鼠标打的断点与显示不一致,那么其实是浏览器就是这样做的,只是vscode少了过渡动画,并不是vscode的bug,是设定。

SourceMap

阮一峰老师的讲解:JavaScript Source Map 详解

简单来说,SourceMap能够让压缩/翻译过的最终版的各种文件与原来未压缩过的文件对应起来。那么断点逻辑容易理解:

  1. 给浏览器提供未压缩的源码和SourceMap文件
  2. 浏览器跑最终版本的js代码时,通过SourceMap找到对应源码的位置,如果该对应源码处被用户打上了断点标记,那么中断

同时:

  1. 仅在你F12打开开发者工具后,浏览器才会去加载最终版代码指示的SourceMap文件(因此基本不影响普通用户加载速度),再根据SourceMap文件指示去加载源码。如下图的文件↓
  2. 线上环境中,能把SourceMap文件也放上去,但不可能把源码也放到服务器。即SourceMap指向源文件位置为/a.js时,浏览器会加载协议://域名/a.js,显然线上没有此文件。但我们可以通过浏览器Sources->Filesystem添加文件夹映射,即映射到本地项目的源码中进行断点调试。当然浏览器窗口会给出相应的ui反馈↓
  3. 视乎SourceMap的质量(如webpack中有多种生成方式,详略程度不一样),可能存在部分映射信息缺乏、模块合并后各处代码指向同一个源码位置等等

vue中配置SourceMap

实际上是设置webpack配置中的devtool:'source-map'选项。这里具体位置出入较大,你可能是@vue/cli初始化的项目、使用webpack的vue模板构建的项目、项目负责人自行重新组织过的项目等,这里就请自行了解到哪配置webpack了。

注意:这个devtool:'source-map'可以说是必须的,其他生成SourceMap的方式都不太适合断点而适合分析,否则由于上述原因出现各种奇怪表现。

webpack提供的本地服务器

在最完整的SourceMap设置下你应该能看到这个结构(a.js是我额外加进去的,不用管)↓ 1.先说下这个webpack://,这个就是webpack自定义的网络应用层协议,跟http://https://一样道理。
2.在这个自定义应用层协议中,可以省略域名(类似于ftp)即webpack:///路径。另外.真的就是一个文件夹,不代表当前目录(http协议中https://juejin.im/timelinehttps://juejin.im/./timeline一回事)
3.如图,webpack:///src中一般是我们的原始vue文件,webpack:///./src是部分处理过后的vue和png等资源
4.webpack本地服务器把编译后资源直接放在内存中,你看不到有编译后的本地文件。因此你可能需要先build一下看看我们的最终打包出来的app.js和他的app.js.map有些什么↓
(1)app.js文件中最后一行会是//# sourceMappingURL=app.js.map,当我们打开开发者工具,浏览器就会去加载这个map文件。
(2)app.js.map里面应该有这样几行:

  "version":3,
  "sources":[
    "webpack:///src/app.js",
    "webpack:///./src/app.js",
    ...
  ]

也就是这个SourceMap能够把编译后的代码映射到sources中指示的若干个js源文件的对应位置。
(3)浏览器可以看作一个强大的IDE,此时我们可以在开发者工具的source面板找到这些文件,并打上断点进行调试

vscode中调试

实质上vscode调试,就是通过调试扩展连接浏览器以获取调试信息,同时通过自己的ui和交互让我们感觉不到在使用上述浏览器功能。(需要安装Debugger for Chrome 这个扩展)

chrome需要的准备功夫

自己命令行启动或者快捷方式,追加启动参数--remote-debugging-port=9222,这个是让chrome打开9222调试端口,我们的vscode扩展会通过这个端口获取浏览器提供的调试信息。下面vscode会以attach方式连接,因此请先启动浏览器,本地跑你的vue项目,并打开目标页面。

Normally, if Chrome is already running when you start debugging with a launch config, then the new instance won't start in remote debugging mode.

注意,如果你已经常规启动chrome,那么再启动一个chrome一般情况下他不会以远程调试模式启动。因此attach方式请关掉所有chrome实例,并且重新以远程调试模式启动一次(如上面参数,可以直接访问http://localhost:9222看看有没有回应)

强制配置的参数

  • type - the type of debugger to use for this launch configuration. Every installed debug extension introduces a type: node for the built-in Node debugger, for example, or php and go for the PHP and Go extensions.(大意是说每个调试扩展都向vscode注册了一个type,vscode根据你设置的type的值去通知不同扩展进行工作)
  • request - the request type of this launch configuration. Currently, launch and attach are supported.(目前只有两个允许值。在我们这个情况下,launch表示启动浏览器打开目标页面并attach;attach即直接连接到当前浏览器的目标页面)
  • name - the reader-friendly name to appear in the Debug launch configuration drop-down.(这个配置的名字。实际就是用来切换多种不同配置的调试)

来源:Debugging

常用的可选参数

可选参数并不统一,需查看各个扩展说明。一些绝大部分扩展都支持的参数官网有说明,不重复了。下面是attach方式中这个扩展常用的参数:

  • url: On a 'launch' config, it will launch Chrome at this URL.(目标url。如果是launch模式,会直接启动浏览器打开这个页面。然后attach这个页面,即相当于在这个页面启动开发者工具)
  • urlFilter: On an 'attach' config, or a 'launch' config with no 'url' set, search for a page with this url and attach to it. It can also contain wildcards, for example, "localhost:*/app" will match either "http://localhost:123/app" or "http://localhost:456/app", but not "stackoverflow.com".(上面那个url只能精确匹配,这个则允许使用通配符,看它的例子)
  • sourceMaps: By default, the adapter will use sourcemaps and your original sources whenever possible. You can disable this by setting sourceMaps to false.(默认为true。可当作上面提到的把目录加入Filesystem)
  • webRoot: 设置${webRoot}变量。${webRoot}是下面两个参数的默认设置用到的变量,从而使得该参数具有意义
  • pathMapping: This property takes a mapping of URL paths to local paths, to give you more flexibility in how URLs are resolved to local files. "webRoot": "${workspaceFolder}" is just shorthand for a pathMapping like { "/": "${workspaceFolder}" }(实际上作用基本同下面,区别主要是人类“语义”上的区别和默认的设置不同。常规文件映射用这个,map文件映射用下面那个,但没有严格区分)
  • sourceMapPathOverrides: 下面会详细说。可简单理解为如何设置当前目录到Filesystem进行映射

来源:vscode-chrome-debug

变量

  • ${workspaceFolder} - the path of the folder opened in VS Code
  • ${workspaceFolderBasename} - the name of the folder opened in VS Code without any slashes (/)
  • ${file} - the current opened file
  • ${relativeFile} - the current opened file relative to workspaceFolder
  • ${fileBasename} - the current opened file's basename
  • ${fileBasenameNoExtension} - the current opened file's basename with no file extension
  • ${fileDirname} - the current opened file's dirname
  • ${fileExtname} - the current opened file's extension
  • ${cwd} - the task runner's current working directory on startup
  • ${lineNumber} - the current selected line number in the active file
  • ${selectedText} - the current selected text in the active file
  • ${execPath} - the path to the running VS Code executable
  • ${defaultBuildTask} - the name of the default build task
详细示例 Predefined variables examples

Supposing that you have the following requirements:

A file located at /home/your-username/your-project/folder/file.ext opened in your editor;

The directory /home/your-username/your-project opened as your root workspace.

So you will have the following values for each variable:

workspaceFolder/home/yourusername/yourproject{workspaceFolder} - /home/your-username/your-project {workspaceFolderBasename} - your-project file/home/yourusername/yourproject/folder/file.ext{file} - /home/your-username/your-project/folder/file.ext {relativeFile} - folder/file.ext relativeFileDirnamefolder{relativeFileDirname} - folder {fileBasename} - file.ext fileBasenameNoExtensionfile{fileBasenameNoExtension} - file {fileDirname} - /home/your-username/your-project/folder fileExtname.ext{fileExtname} - .ext {lineNumber} - line number of the cursor selectedTexttextselectedinyourcodeeditor{selectedText} - text selected in your code editor {execPath} - location of Code.exe

来源:Variables Reference 这个点进去看他举例就能理解了,不翻译了。

sourceMapPathOverrides

设置如何映射。下面是它的默认设置

// Note: These are the mappings that are included by default out of the box, with examples of how they could be resolved in different scenarios. These are not mappings that would make sense together in one project.
// webRoot = /Users/me/project
"sourceMapPathOverrides": {
    "webpack:///./~/*": "${webRoot}/node_modules/*",       // Example: "webpack:///./~/querystring/index.js" -> "/Users/me/project/node_modules/querystring/index.js"
    "webpack:///./*":   "${webRoot}/*",                    // Example: "webpack:///./src/app.js" -> "/Users/me/project/src/app.js",
    "webpack:///*":     "*",                               // Example: "webpack:///project/app.ts" -> "/project/app.ts"
    "webpack:///src/*": "${webRoot}/*",                    // Example: "webpack:///src/app.js" -> "/Users/me/project/app.js"
    "meteor://💻app/*": "${webRoot}/*"                    // Example: "meteor://💻app/main.ts" -> "/Users/me/project/main.ts"
}

其中的${webRoot}是我们自定义值的一个变量。你需要做的,就是保证webpack:///下的各种文件能被正确映射到我们的开发目录文件
注意:sourceMapPathOverrides一旦被自定义设置,那么原来的默认设置无效(就是被重新赋值的意思);存在多个匹配时,后面出现匹配的覆盖前面存在的匹配。

总结

搞了一大堆,其实就是这么几个意思:
1.chrome远程调试模式启动,先确保你知道是哪个端口可被vscode连接、确保你能在F12里面断点不会有奇奇怪怪的现象
2.vscode创建调试,在launch.json中,点击右下角的添加配置,更改端口port为上面你启动的端口
3.设置url或者urlFilter参数(attach要求你已经打开了这个url对应的页面。launch方式就是多了自动帮你打开这个页面,没多大区别)
4.设置webRoot参数,因为默认的sourceMapPathOverrides中用到了${webRoot} 这个变量
5.等一会,attach上目标页面后,vscode底部栏会变色,可以了。热更新后有问题请刷新页面
6.理解为重,最常见错误是webRoot与sourceMapPathOverrides胡乱设置没有对应上。一般而言,只要你的vue项目是工作目录下有个src文件夹,里面有main.js、App.vue这样结构的话,那么最小化的配置只要6个参数:

{
    "type": "chrome",
    "request": "attach",
    "name": "hello world",
    "port": 9222,
    "webRoot": "${workspaceFolder}/src",
    "url": "http://localhost:8080"
}