VSCode在Vue项目中断点调试

如何在 VSCode 中设置断点,直接调试 Vue 代码?

以下所有运行环境的 node 版本:v16.15.1

1 vue-cli 4 创建的 vue 项目

以下操作和设置,在由 vue-cli 4 创建的 vue2/js 项目下进行,应该也适用于 vue-cli 4 下创建的其它类型项目,没有进一步尝试。

vue-cli 4 对应的 webpack 版本为 4.x。

一些前置条件

VSCode 已经内置 JaveScript Debugger,不需要再安装 Debugger for Chome。

microsoft/vscode-js-debug: The VS Code JavaScript debugger

tiaoshi-1

VSCode 需要配置 launch.json

Debugging in Visual Studio Code

在项目中,添加 launch.json 文件,可以选择多种调试器。

tiaoshi-2

如果已经创建,则还可以添加配置。

tiaoshi-3

这里是配置属性的含义说明:

Debugging in Visual Studio Code

launch.json 配置

需要添加 sourceMapPathOverrides

vscode-js-debug/OPTIONS.md at ab0b7d74c66a837c2ab6918f7bb81a8fc3c61663 · microsoft/vscode-js-debug

{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Chrome",
"request": "launch",
"type": "chrome",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}/src",
"sourceMapPathOverrides": {
"webpack:///src/*": "${webRoot}/*"
}
}
]
}

注意 webRoot 要设置正确,需要和 sourceMapPathOverrides 中的路径对应。

vue.config.js 配置

webpack 相关 | Vue CLI

添加 vue.config.js 配置文件,添加 webpack 配置。

module.exports = {
configureWebpack: {
devtool: 'source-map'
}
}

调试运行

在终端中使用 yarn serve 运行,然后在 vscode 启动调试,就可以命中断点了。

tiaoshi-4

2 vue-cli 5 创建的 vue 项目

vue-cli 5 对应的 webpack 版本为 5.x,在 vue-cli 5 下创建的 vue 项目,不能通过类似上面比较简单的设置,就开启在 VSCode 中的断点调试支持。

以下的配置在 vue2/js,vue3/js,vue3/ts 下测试 OK,但操作有点复杂,不像是支持一个调试功能需要的设置,不然门槛有点太高了。如果知道其它简单的方式,求告知。

方案来源:Vue + TypeScript & Debuggers - ckh|Consulting

准备工作

添加 source-map 包,被安装的版本为 “^0.7.4”。

yarn add source-map -D

launch.json 配置

{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Chrome",
"request": "launch",
"type": "chrome",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}",
"sourceMapPathOverrides": {
"webpack://<name-from-package.json>/./src/*": "${webRoot}/src/*"
},
}
]
}

注意,上面的 <name-from-package.json> 需要替换为项目名称

vue.config.js

const { defineConfig } = require('@vue/cli-service')
const fs = require('fs')
const { SourceMapConsumer, SourceMapGenerator } = require('source-map')

module.exports = defineConfig({
transpileDependencies: true,
configureWebpack: {
devtool: 'source-map',
plugins: [
{
apply(compiler) {
compiler.hooks.thisCompilation.tap('Initializing Compilation', compilation => {
compilation.hooks.finishModules.tapPromise('All Modules Built', async modules => {
for (const module of modules) {
if (shouldSkipModule(module)) continue

const pathWithoutQuery = module.resource.replace(/\?.*$/, '')
const sourceFile = fs.readFileSync(pathWithoutQuery).toString('utf-8')
const sourceMap = extractSourceMap(module)

sourceMap.sources = [pathWithoutQuery]
sourceMap.sourcesContent = [sourceFile]
sourceMap.mappings = await shiftMappings(sourceMap, sourceFile, pathWithoutQuery)
}
})
})
}
}
]
}
})

function shouldSkipModule(module) {
const { resource = '' } = module

if (!resource) return true
if (/node_modules/.test(resource)) return true
if (!/\.vue/.test(resource)) return true
if (!/type=script/.test(resource)) return true
if (!/lang=ts/.test(resource)) return true
if (isMissingSourceMap(module)) return true

return false
}

function isMissingSourceMap(module) {
return !extractSourceMap(module)
}

function extractSourceMap(module) {
if (!module['_source']) return null

return module['_source']['_sourceMap'] || module['_source']['_sourceMapAsObject'] || null
}

async function shiftMappings(sourceMap, sourceFile, sourcePath) {
const indexOfScriptTag = getIndexOfScriptTag(sourceFile)

const shiftedSourceMap = await SourceMapConsumer.with(sourceMap, null, async consumer => {
const generator = new SourceMapGenerator()

consumer.eachMapping(mapping => {
const { generatedColumn, generatedLine, originalColumn, originalLine } = mapping

let name = mapping.name
let source = sourcePath

var original = null

if (originalLine === null || originalColumn === null) {
name = null
source = null
} else {
original = {
column: originalColumn,
line: originalLine + indexOfScriptTag
}
}

generator.addMapping({
generated: {
column: generatedColumn,
line: generatedLine
},
original,
source,
name
})
})

return generator.toJSON()
})

return shiftedSourceMap.mappings
}

function getIndexOfScriptTag(sourceFile) {
const lines = sourceFile.match(/.+/g)
let indexOfScriptTag = 0

for (const line of lines) {
++indexOfScriptTag
if (/<script/.test(line)) break
}

return indexOfScriptTag
}

以上代码还是来自 Vue + TypeScript & Debuggers - ckh|Consulting,源码见:vue-typescript-debugger/vue.config.js at master · fearnycompknowhow/vue-typescript-debugger

需要注意的是,以上源码,做了一点小修复:添加了 var original = null 的定义,不然编译可能报错。

运行调试

通过 yarn serve 运行项目,在 VSCode 中启动调试,就可以命中断点了。

注意,launch.json 的配置的启动 url,需要随配置修改。

3 vite 创建的 vue 项目

这里的 vite 版本是 2.9.13

npm info vite version

如果使用 vite 创建 vue 项目,不管是 vue2、vue3,还是 js/ts,让 VSCode 支持调试,都非常简单。

开始 | Vite 官方中文文档

yarn create vite

launch.json

只要添加 “webRoot”: “${workspaceFolder}/src” 就可以了

{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Chrome",
"request": "launch",
"type": "chrome",
"url": "http://localhost:3000",
"webRoot": "${workspaceFolder}/src"
}
]
}

运行调试

然后直接运行,yarn dev,接着就可以在 VSCode 中启动调试了。

tiaoshi-5

4. 参考文章

在 VS Code 中调试 Vue.js - SegmentFault 思否

Vue + TypeScript & Debuggers - ckh|Consulting