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 内)是这样的:

也就是说,我有一个服务于 vueJS 前端的 Java 后端,所有这些都捆绑在一个 Tomcat 网络存档中(即 war-文件).这实际上工作得很好,我可以在 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]):/(.+)": ":/",
        "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 布尔值,而是指定 inlineSourceMapinlineSources。我已经注释掉了这些设置,因为我无法确定它们是否有帮助。

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 的最佳支持环境