Debugging in VS Code does not work for Typescript Vue app

Viewed 3579

I generated a new Vue app out of the box like this:

? Please pick a preset: Manually select features
? Check the features needed for your project: Choose Vue version, Babel, TS, PWA, Router, Vuex, CSS Pre-processors, Linter, Unit, E2E
? Choose a version of Vue.js that you want to start the project with 3.x (Preview)
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with dart-sass)
? Pick a linter / formatter config: Prettier
? Pick additional lint features: Lint on save, Lint and fix on commit
? Pick a unit testing solution: Jest
? Pick an E2E testing solution: WebdriverIO
? Pick browsers to run end-to-end test on Chrome
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No

Added a launch.json to VS Code like this:

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "chrome",
      "request": "launch",
      "name": "vuejs: chrome",
      "url": "http://localhost:8080",
      "webRoot": "${workspaceFolder}/src",
      "breakOnLoad": true,
      "sourceMapPathOverrides": {
        "webpack:///src/*": "${webRoot}/*"
      }
    }
  ]
}

Added a vue.config.js to my project root like this:

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

Put a breakpoint in main.ts at:

createApp(App)
  .use(store)
  .use(router)
  .mount("#app");

Ran npm run serve from the command line and hit F5 in VS Code. No break. How can I troubleshoot this? Is Typescript just not suitable for Vue? I've had JS projects work before.

4 Answers

Unfortunately, none of the other answers worked for me. Maybe it was because I was using Single File Components (SFC) and they were not?

After spending hours digging into this problem, I think I've found out where it stems from:

During the build process, Vue will split your SFCs into their individual parts (<template>, <script>, and <style>) and run those parts through the appropriate loaders.

When your SFCs use TypeScript, the <script> portion of those SFCs are compiled using the TypeScript Compiler (TSC). The problem is that the TSC has no idea that the code it is compiling actually originated from your SFC, because all it sees is the content from the <script> portion after it has been segregated by the Vue compiler.

Because of this, the TSC generates Source Maps that contain improper line numbers (they are not shifted down to compensate for the <template> portion of you SFC), improper source file references (the filenames contain hashes that are placed there by the Vue compiler), and improper source file content (the content will contain the <script> code only and will be missing the <template> and <style> code).

Unfortunately, I can't describe how to fix the problem here, as it would make this post very long, but I can redirect you to a blog post I wrote about the topic.

In short, the way I fixed the problem was by leveraging the Webpack build process to correct the broken Source Maps.

Try using the snippet below for your sourceMapPathOverrides

"sourceMapPathOverrides": {
        "webpack:///src/*.vue": "${webRoot}/*.vue",
        "webpack:///./src/*.ts": "${webRoot}/*.ts",
     }

Follow this recipe and it should solve your problem - https://github.com/microsoft/vscode-recipes/tree/master/vuejs-cli

Your launch.json needs to look more like this (omit the preLaunchTask unless you define it in tasks.json):

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "chrome",
      "request": "launch",
      "name": "vuejs: chrome",
      "url": "http://localhost:8080",
      "webRoot": "${workspaceFolder}",
      "breakOnLoad": true,
      "pathMapping": {
        "/_karma_webpack_": "${workspaceFolder}"
      },
      "sourceMapPathOverrides": {
        "webpack:/*": "${webRoot}/*",
        "/./*": "${webRoot}/*",
        "/src/*": "${webRoot}/*",
        "/*": "*",
        "/./~/*": "${webRoot}/node_modules/*"
      },
      "preLaunchTask": "vuejs: start"
    }
  ]
}

After trying multiple solutions, this was the only one that worked. author ref

I just put this in vue.config.js and my debug finally worked.


/* eslint-disable */
const CompilerSfc = require('@vue/compiler-sfc')
const parse = CompilerSfc.parse
CompilerSfc.parse = (source, options) => {
    return parse(source, Object.assign({ pad: true }, options))
}

module.exports = {
    configureWebpack: {
        output: {
            devtoolModuleFilenameTemplate: (info) => {
                if (info.allLoaders === '') {
                    // when allLoaders is an empty string the file is the original source
                    // file and will be prefixed with src:// to provide separation from
                    // modules transpiled via webpack
                    const filenameParts = ['src://']
                    if (info.namespace) {
                        filenameParts.push(info.namespace + '/')
                    }
                    filenameParts.push(info.resourcePath.replace(/^\.\//, ''))
                    return filenameParts.join('')
                } else {
                    // otherwise we have a webpack module
                    const filenameParts = ['webpack://']
                    if (info.namespace) {
                        filenameParts.push(info.namespace + '/')
                    }
                    filenameParts.push(info.resourcePath.replace(/^\.\//, ''))
                    const isVueScript =
                        info.resourcePath.match(/\.vue$/) &&
                        info.query.match(/\btype=script\b/) &&
                        !info.allLoaders.match(/\bts-loader\b/)
                    if (!isVueScript) {
                        filenameParts.push('?' + info.hash)
                    }
                    return filenameParts.join('')
                }
            },
        },
    },
}
Related