【问题标题】:debug-vuejs-from-vs-code: unbound breakpoint when debugging vueJS app in chromedebug-vuejs-from-vs-code:在 chrome 中调试 vueJS 应用程序时未绑定断点
【发布时间】:2021-11-23 12:02:03
【问题描述】:

版本:

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 网络存档(即战争文件)中。这实际上效果很好,我可以在 VS Studio Code 中使用 Tomcat 扩展来调试 Java 代码。

问题在于调试 vueJS 逻辑。请注意,我的 vueJS 应用程序包含 TypeScript 插件。 debug-vuejs-from-vs-code 启动良好(在正确的调试配置下,如下所示),我可以设置一个浏览器断点,它实际上会在 IDE 中触发中断。所以 VS Studio Code 和 Chrome 之间的基本握手是合理的。因此,我的怀疑是配置——即,launch.jsontsconfig.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]):/(.+)": "$1:/$2",
        "webpack:///src/*": "${webRoot}/src/*",
      }
    }
  ]
}

这里,${workspaceFolder} 只是对应于我的源代码库的根目录。 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 布尔值,而是指定 inlineSourceMapinlineSources。我已将这些设置注释掉,因为我无法确定它们是否有帮助。

vueJS-build 生成输出到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 Code 中的 vueJS 应用程序出现未绑定断点。

你看到问题了吗?

【问题讨论】:

  • 我访问了菜单项View>Command Palette,然后找到Debug: Diagnose Breakpoint Problems。如果我点击链接Why my breakpoints don't bind,我会看到我的断点(带有原始源代码行)、断点未绑定的原因以及可能用于映射目的的候选位置。事实上,提示是存在路径和/或映射配置问题。

标签: typescript visual-studio-code vuejs3 vscode-debugger


【解决方案1】:

launch.json 属性 sourceMapPathOverridesVS Code documentation 很少。特别是,我找不到映射覆盖的任何语法规则。但是,根据之前的评论,访问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) 用于 vueJS 3 的 TypeScript 插件 4.1.x。 x -- 根本不是通过 IDE 调试客户端 javascript 的最佳支持环境。

【讨论】:

    猜你喜欢
    • 2021-10-05
    • 2022-12-23
    • 2021-10-15
    • 2019-07-02
    • 2021-02-24
    • 2022-08-19
    • 2020-12-06
    • 1970-01-01
    相关资源
    最近更新 更多