debug-vuejs-from-vs-code:在 chrome 中调试 vueJS 应用程序时未绑定断点
debug-vuejs-from-vs-code: unbound breakpoint when debugging vueJS app in chrome
版本:
Visual Studio Code: Version: 1.59.1 (Universal)
vueJS: 3.0.0
Chrome: Version 94.0.4606.61 (Official Build) (x86_64)
我正在使用 VS Studio Code 中内置的 javascript 调试器。我的应用程序结构(在 IDE 内)是这样的:
- 根目录(Maven 父项目)
- 后端(Java 中的 Maven 子项目)
- 前端(vueJS 中的 Maven 子项目)
也就是说,我有一个服务于 vueJS 前端的 Java 后端,所有这些都捆绑在一个 Tomcat 网络存档中(即 war-文件).这实际上工作得很好,我可以在 VS Studio Code 中使用 Tomcat 扩展调试 Java 代码。
问题是调试vueJS逻辑。请注意,我的 vueJS 应用程序包含 TypeScript 插件。 debug-vuejs-from-vs-code 启动良好(在正确的调试配置下,如下所示),我可以设置一个浏览器断点,它实际上会触发 IDE。所以 VS Studio Code 和 Chrome 之间的基本握手是合理的。因此,我怀疑配置——即 launch.json、tsconfig.json 或其他一些 IDE 设置——不太对。详情如下。
vue.config.json:
module.exports = {
// Change build paths to make them Maven compatible; see https://cli.vuejs.org/config/.
outputDir: 'target/dist',
assetsDir: 'static',
publicPath: '/myapp',
configureWebpack: {
devtool: "source-map"
}
}
在这里,我为生产中的 webpack 缩小文件启用了源代码映射(即,Chrome 中的客户端脚本 运行)。请注意,我的应用程序植根于 Chrome.
中的 /myapp
launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"name": "vuejs: chrome",
"request": "launch",
"url": "http://localhost:8080/myapp",
"breakOnLoad": true,
"webRoot": "${workspaceFolder}/frontend",
"outFiles": ["${workspaceFolder}/frontend/target/dist/static/js/*.js"],
"vueComponentPaths": [
"${workspaceFolder}/frontend/src/**/*.vue"
],
"sourceMaps": true,
"sourceMapPathOverrides": {
"webpack:///myapp/static/js/*": "${webRoot}/src/*",
"webpack:///./~/*": "${webRoot}/node_modules/*",
"webpack:////*": "/*",
"webpack://?:*/*": "${webRoot}/src/*",
"webpack:///([a-z]):/(.+)": ":/",
"webpack:///src/*": "${webRoot}/src/*",
}
}
]
}
这里,${workspaceFolder}
正好对应我的source-repo的根目录。 sourceMapPathOverrides
目前一团糟——默认映射和我自己尝试(因此没有结果)将 Chrome 端 javascript 资源路径映射到引用的源回购路径的组合在 IDE.
ts.config.json:
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
// "inlineSourceMap": true,
// "inlineSources": true,
"sourceMap": true,
"baseUrl": ".",
"resolveJsonModule": true,
"types": [
"webpack-env",
"jest"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}
一位程序员建议删除 sourceMap
布尔值,而是指定 inlineSourceMap
和 inlineSources
。我已经注释掉了这些设置,因为我无法确定它们是否有帮助。
vueJS-build 使用这种 directory/file 布局生成 target/dist
的输出:
target/dist
target/dist/favicon.ico
target/dist/index.html
target/dist/static
target/dist/static/css
target/dist/static/css/chunk-vendors.0f1ada3b.css
target/dist/static/css/app.b6011a27.css
target/dist/static/js
target/dist/static/js/app.74994c71.js.map
target/dist/static/js/chunk-vendors.377aa5d2.js
target/dist/static/js/chunk-vendors.377aa5d2.js.map
target/dist/static/js/app.74994c71.js
target/dist/static/img
target/dist/static/img/logo.82b9c7a5.png
总而言之,我相信内置的 javascript 调试器工作正常,但是有一个配置错误导致 Visual Studio 代码中的 vueJS 应用程序出现未绑定断点。
你看到问题了吗?
VS Code documentation launch.json
属性 sourceMapPathOverrides
不足。特别是,我找不到映射覆盖的任何语法规则。但是,根据之前的评论,访问 Debug: Diagnose Breakpoint Problems
提供的面板中的 VS Code 链接确实会带来有用的解释和提示。因此,我能够发现错误的映射,现在,已经通过这种方式解决了它们:
"sourceMapPathOverrides": {
"webpack:///./node_modules": "${webRoot}/node_modules",
"webpack:///./src/*": "${webRoot}/src/*"
}
我的整个 launch.json
现在显示:
{
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"name": "vuejs: chrome",
"request": "launch",
"url": "http://localhost:8080/myapp",
"breakOnLoad": true,
"webRoot": "${workspaceFolder}/frontend",
"pathMapping": {
"/_karma_webpack_": "${workspaceFolder}/frontend"
},
"outFiles": ["${workspaceFolder}/frontend/target/dist/**/*.js"],
"vueComponentPaths": [
"${workspaceFolder}/frontend/src/**/*.vue"
],
"sourceMaps": true,
"sourceMapPathOverrides": {
"webpack:///./node_modules": "${webRoot}/node_modules",
"webpack:///./src/*": "${webRoot}/src/*"
}
}
]
}
通过这些映射,我至少能够让 JS 调试器“在加载时中断”。但是,将源代码行与浏览器端、缩小的 JS 逻辑相关联的问题仍然存在。为了解决这个问题,我将逻辑 outlined here 合并到 vue.config.js
:
const { SourceMapConsumer, SourceMapGenerator } = require('source-map');
const sourceMaps = {};
module.exports = {
// Change build paths to make them Maven compatible; see https://cli.vuejs.org/config/.
outputDir: 'target/dist',
assetsDir: 'static',
publicPath: '/myapp',
configureWebpack() {
return {
devtool: 'source-map',
plugins: [{
apply(compiler) {
compiler.hooks.thisCompilation.tap('Initializing Compilation', (compilation) => {
compilation.hooks.succeedModule.tap('Module Built', (module) => {
const { resource } = module;
if (!resource) return;
if (/node_modules/.test(resource)) return;
if (!/\.vue/.test(resource)) return;
if (!/type=template/.test(resource)) return;
if (!module['_source'] || !module['_source']['_sourceMap']) return;
const pathWithoutQuery = module.resource.replace(/\?.*$/, '');
sourceMaps[pathWithoutQuery] = module['_source']['_sourceMap'];
});
compilation.hooks.finishModules.tapPromise('All Modules Built', async (modules) => {
for (const module of modules) {
const { resource } = module;
if (!resource) continue;
if (/node_modules/.test(resource)) continue;
if (!/\.vue/.test(resource)) continue;
if (!/type=script/.test(resource)) continue;
if (!/lang=ts/.test(resource)) continue;
if (!module['_source'] || !module['_source']['_sourceMap']) continue;
const pathWithoutQuery = module.resource.replace(/\?.*$/, '');
const templateSourceMap = sourceMaps[pathWithoutQuery];
if (!templateSourceMap) continue;
const scriptSourceMap = module['_source']['_sourceMap'];
scriptSourceMap.sourcesContent = [...templateSourceMap.sourcesContent];
scriptSourceMap.sources = [...templateSourceMap.sources];
const lines = (templateSourceMap.sourcesContent[0] || '').match(/.+/g);
let indexOfScriptTag = 0;
for (const line of lines) {
++indexOfScriptTag;
if (/<script/.test(line)) break;
}
const shiftedSourceMap = await SourceMapConsumer.with(scriptSourceMap, null, async (consumer) => {
const generator = new SourceMapGenerator();
await consumer.eachMapping((mapping) => {
const {
generatedColumn,
generatedLine,
originalColumn,
originalLine
} = mapping;
let name = mapping.name;
let source = templateSourceMap.sources[0] || 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();
});
scriptSourceMap.mappings = shiftedSourceMap.mappings;
}
});
});
}
}]
};
}
}
使用此设置,我可以从 VS Code 启动 pwa-chrome
调试器并在 IDE.
中单步执行 vueJS 组件逻辑
尽管如此,我决定这种特殊的技术组合——即 (i) vscode-js-debugger,(ii) vueJS 3.x,以及 (iii) 和 TypeScript plugin 4.1.x for vueJS 3.x —— 根本不是通过 IDE.
调试客户端 javascript 的最佳支持环境
版本:
Visual Studio Code: Version: 1.59.1 (Universal)
vueJS: 3.0.0
Chrome: Version 94.0.4606.61 (Official Build) (x86_64)
我正在使用 VS Studio Code 中内置的 javascript 调试器。我的应用程序结构(在 IDE 内)是这样的:
- 根目录(Maven 父项目)
- 后端(Java 中的 Maven 子项目)
- 前端(vueJS 中的 Maven 子项目)
也就是说,我有一个服务于 vueJS 前端的 Java 后端,所有这些都捆绑在一个 Tomcat 网络存档中(即 war-文件).这实际上工作得很好,我可以在 VS Studio Code 中使用 Tomcat 扩展调试 Java 代码。
问题是调试vueJS逻辑。请注意,我的 vueJS 应用程序包含 TypeScript 插件。 debug-vuejs-from-vs-code 启动良好(在正确的调试配置下,如下所示),我可以设置一个浏览器断点,它实际上会触发 IDE。所以 VS Studio Code 和 Chrome 之间的基本握手是合理的。因此,我怀疑配置——即 launch.json、tsconfig.json 或其他一些 IDE 设置——不太对。详情如下。
vue.config.json:
module.exports = {
// Change build paths to make them Maven compatible; see https://cli.vuejs.org/config/.
outputDir: 'target/dist',
assetsDir: 'static',
publicPath: '/myapp',
configureWebpack: {
devtool: "source-map"
}
}
在这里,我为生产中的 webpack 缩小文件启用了源代码映射(即,Chrome 中的客户端脚本 运行)。请注意,我的应用程序植根于 Chrome.
中的/myapp
launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"name": "vuejs: chrome",
"request": "launch",
"url": "http://localhost:8080/myapp",
"breakOnLoad": true,
"webRoot": "${workspaceFolder}/frontend",
"outFiles": ["${workspaceFolder}/frontend/target/dist/static/js/*.js"],
"vueComponentPaths": [
"${workspaceFolder}/frontend/src/**/*.vue"
],
"sourceMaps": true,
"sourceMapPathOverrides": {
"webpack:///myapp/static/js/*": "${webRoot}/src/*",
"webpack:///./~/*": "${webRoot}/node_modules/*",
"webpack:////*": "/*",
"webpack://?:*/*": "${webRoot}/src/*",
"webpack:///([a-z]):/(.+)": ":/",
"webpack:///src/*": "${webRoot}/src/*",
}
}
]
}
这里,${workspaceFolder}
正好对应我的source-repo的根目录。 sourceMapPathOverrides
目前一团糟——默认映射和我自己尝试(因此没有结果)将 Chrome 端 javascript 资源路径映射到引用的源回购路径的组合在 IDE.
ts.config.json:
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
// "inlineSourceMap": true,
// "inlineSources": true,
"sourceMap": true,
"baseUrl": ".",
"resolveJsonModule": true,
"types": [
"webpack-env",
"jest"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}
一位程序员建议删除 sourceMap
布尔值,而是指定 inlineSourceMap
和 inlineSources
。我已经注释掉了这些设置,因为我无法确定它们是否有帮助。
vueJS-build 使用这种 directory/file 布局生成 target/dist
的输出:
target/dist
target/dist/favicon.ico
target/dist/index.html
target/dist/static
target/dist/static/css
target/dist/static/css/chunk-vendors.0f1ada3b.css
target/dist/static/css/app.b6011a27.css
target/dist/static/js
target/dist/static/js/app.74994c71.js.map
target/dist/static/js/chunk-vendors.377aa5d2.js
target/dist/static/js/chunk-vendors.377aa5d2.js.map
target/dist/static/js/app.74994c71.js
target/dist/static/img
target/dist/static/img/logo.82b9c7a5.png
总而言之,我相信内置的 javascript 调试器工作正常,但是有一个配置错误导致 Visual Studio 代码中的 vueJS 应用程序出现未绑定断点。
你看到问题了吗?
VS Code documentation launch.json
属性 sourceMapPathOverrides
不足。特别是,我找不到映射覆盖的任何语法规则。但是,根据之前的评论,访问 Debug: Diagnose Breakpoint Problems
提供的面板中的 VS Code 链接确实会带来有用的解释和提示。因此,我能够发现错误的映射,现在,已经通过这种方式解决了它们:
"sourceMapPathOverrides": {
"webpack:///./node_modules": "${webRoot}/node_modules",
"webpack:///./src/*": "${webRoot}/src/*"
}
我的整个 launch.json
现在显示:
{
"version": "0.2.0",
"configurations": [
{
"type": "pwa-chrome",
"name": "vuejs: chrome",
"request": "launch",
"url": "http://localhost:8080/myapp",
"breakOnLoad": true,
"webRoot": "${workspaceFolder}/frontend",
"pathMapping": {
"/_karma_webpack_": "${workspaceFolder}/frontend"
},
"outFiles": ["${workspaceFolder}/frontend/target/dist/**/*.js"],
"vueComponentPaths": [
"${workspaceFolder}/frontend/src/**/*.vue"
],
"sourceMaps": true,
"sourceMapPathOverrides": {
"webpack:///./node_modules": "${webRoot}/node_modules",
"webpack:///./src/*": "${webRoot}/src/*"
}
}
]
}
通过这些映射,我至少能够让 JS 调试器“在加载时中断”。但是,将源代码行与浏览器端、缩小的 JS 逻辑相关联的问题仍然存在。为了解决这个问题,我将逻辑 outlined here 合并到 vue.config.js
:
const { SourceMapConsumer, SourceMapGenerator } = require('source-map');
const sourceMaps = {};
module.exports = {
// Change build paths to make them Maven compatible; see https://cli.vuejs.org/config/.
outputDir: 'target/dist',
assetsDir: 'static',
publicPath: '/myapp',
configureWebpack() {
return {
devtool: 'source-map',
plugins: [{
apply(compiler) {
compiler.hooks.thisCompilation.tap('Initializing Compilation', (compilation) => {
compilation.hooks.succeedModule.tap('Module Built', (module) => {
const { resource } = module;
if (!resource) return;
if (/node_modules/.test(resource)) return;
if (!/\.vue/.test(resource)) return;
if (!/type=template/.test(resource)) return;
if (!module['_source'] || !module['_source']['_sourceMap']) return;
const pathWithoutQuery = module.resource.replace(/\?.*$/, '');
sourceMaps[pathWithoutQuery] = module['_source']['_sourceMap'];
});
compilation.hooks.finishModules.tapPromise('All Modules Built', async (modules) => {
for (const module of modules) {
const { resource } = module;
if (!resource) continue;
if (/node_modules/.test(resource)) continue;
if (!/\.vue/.test(resource)) continue;
if (!/type=script/.test(resource)) continue;
if (!/lang=ts/.test(resource)) continue;
if (!module['_source'] || !module['_source']['_sourceMap']) continue;
const pathWithoutQuery = module.resource.replace(/\?.*$/, '');
const templateSourceMap = sourceMaps[pathWithoutQuery];
if (!templateSourceMap) continue;
const scriptSourceMap = module['_source']['_sourceMap'];
scriptSourceMap.sourcesContent = [...templateSourceMap.sourcesContent];
scriptSourceMap.sources = [...templateSourceMap.sources];
const lines = (templateSourceMap.sourcesContent[0] || '').match(/.+/g);
let indexOfScriptTag = 0;
for (const line of lines) {
++indexOfScriptTag;
if (/<script/.test(line)) break;
}
const shiftedSourceMap = await SourceMapConsumer.with(scriptSourceMap, null, async (consumer) => {
const generator = new SourceMapGenerator();
await consumer.eachMapping((mapping) => {
const {
generatedColumn,
generatedLine,
originalColumn,
originalLine
} = mapping;
let name = mapping.name;
let source = templateSourceMap.sources[0] || 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();
});
scriptSourceMap.mappings = shiftedSourceMap.mappings;
}
});
});
}
}]
};
}
}
使用此设置,我可以从 VS Code 启动 pwa-chrome
调试器并在 IDE.
尽管如此,我决定这种特殊的技术组合——即 (i) vscode-js-debugger,(ii) vueJS 3.x,以及 (iii) 和 TypeScript plugin 4.1.x for vueJS 3.x —— 根本不是通过 IDE.
调试客户端 javascript 的最佳支持环境