debug-vuejs-from-vs-code:在 chrome 中调试 vueJS 应用程序时未绑定断点

Posted

技术标签:

【中文标题】debug-vuejs-from-vs-code:在 chrome 中调试 vueJS 应用程序时未绑定断点【英文标题】:debug-vuejs-from-vs-code: unbound breakpoint when debugging vueJS app in chrome 【发布时间】: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,我会看到我的断点(带有原始源代码行)、断点未绑定的原因以及可能用于映射目的的候选位置。事实上,提示是存在路径和/或映射配置问题。 【参考方案1】:

launch.json 属性 sourceMapPathOverrides 的VS 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 的最佳支持环境。

【讨论】:

以上是关于debug-vuejs-from-vs-code:在 chrome 中调试 vueJS 应用程序时未绑定断点的主要内容,如果未能解决你的问题,请参考以下文章