Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate distinct paths for supporting .vue files in sourcemaps #11023

Open
connor4312 opened this issue Jan 17, 2020 · 21 comments
Open

Generate distinct paths for supporting .vue files in sourcemaps #11023

connor4312 opened this issue Jan 17, 2020 · 21 comments

Comments

@connor4312
Copy link

What problem does this feature solve?

Hello Vue 👋

I'm working on our new JavaScript debugger for VS Code, and ran into some problems when trying out Vue. The default vue-cli setup creates several supporting .vue scripts. For instance, open the Chrome devtools and ctrl p for "App.vue" in a new project. The top entry is the correct Vue file, the others aren't.

From the VS Code perspective, the user might ask us to set a breakpoint in a Vue file. We know which file the breakpoint is attached to on disk, and we need to figure out which loaded file that breakpoint gets mapped to. However, because all these paths are quite similar and all (in the context of a generic web app) possibly the file we want, we end up putting the breakpoint in the wrong file and breaking in the incorrect place. And often the supporting scripts evaluate before the 'real' script does, so we can't, for example, wait and pick the best match from the possible candidates.

What does the proposed API look like?

The simplest solution would be to prefix the supporting files with some path like __vue__, or something along those lines. This would prevent the paths from incorrectly mapping to files that exist on disk.

@posva
Copy link
Member

posva commented Jan 18, 2020

Maybe @znck or @sodatea know more about where to do something here

@znck
Copy link
Member

znck commented Jan 18, 2020

This happens in vue-loader, right now I don’t have bandwidth to take it but I can guide if someone is interested.

@connor4312
Copy link
Author

I could probably find some time to help out. Code pointers or guidance would be appreciated 🙂

@connor4312
Copy link
Author

This is turning out to be a little more tricky than I thought. Convincing webpack to resolve paths that don't actually exist on the filesystem is difficult; I've made some headway with the virtual-module-webpack-plugin, though it's somewhat dangerous and there's more work to do to have imports from adjusted modules resolve correctly, I'm not really happy with this solution. Maybe there's a way to tweak change the locations in the sourcemaps for these files without needing to ask Webpack to treat them as entirely different disk files.

I've pushed some ugly scratch work to https://github.com/connor4312/vue-loader

connor4312 added a commit to microsoft/vscode-js-debug that referenced this issue Jun 11, 2020
> Observed + tested with Vue CLI 3 and 4

Vue does two weird things:

 1. For each .vue file, they flatten directory structure and only output
    `webpack:///foo.vue`, for instance.
 2. They have generated code in the sourcemaps, which do things like
    create CSS and DOM. Ironically, these files _are_ structured
    correctly (e.g. `webpack:///component/foo.vue`) but we don't want
    to break in them.

This PR introduces some special handling those. Since the directory
structure needs to be restored, that entails we have to do some kind of
re-mapping back to files on disk. I've made the search classes we use
for breakpoint prediction reusable for this: the first time we get a Vue
file that needs remapping, we'll scan the workspace and create the
appropriate mapping.

I have added two regexes that, as precisely as I can, target the build
outputs from the Vue CLI to trigger this remapping when necessary, and
to ignore the nested generated Vue files.

Fixes #239
Refs vuejs/vue#11023
@connor4312
Copy link
Author

connor4312 commented Jul 12, 2020

In microsoft/vscode-js-debug@2f3c93a I introduced a heuristic that seems to work very well for plain Vue with JS files, where the path is different (as in the screenshot above). However, with lang="ts" in vue components the path of the generated code is entirely indistinguishable from that of the generated code.

E.g., only one of these is the right one:

Without doing some kind of source code analysis, which is complex and fragile, I can't deterministically tell which one is the 'right' script to map back to the file.

@sirlancelot
Copy link

Would be neat to see this fixed as well. It's difficult to know which one is correct even as a human looking at Chrome Devtools 🤷

@connor4312
Copy link
Author

@znck let me know what you think a good solution is. We've gotten continued reports of people running into this with VS Code 🙂

@ariyuan
Copy link

ariyuan commented Sep 24, 2020

Looking forward to the fix

@jtsom
Copy link

jtsom commented Sep 24, 2020

It's been almost 10 months with no indication of it being fixed...

@connor4312
Copy link
Author

connor4312 commented Oct 30, 2020

@znck I would appreciate your input here. This continues to be a pain point for Vue users on VS Code. (most recently in microsoft/vscode-js-debug#754)

@znck
Copy link
Member

znck commented Nov 2, 2020

It is quite tricky to make source maps and hot reload work simultaneously. I understand it's a huge pain point and I'll revisit it (hopefully coming weekend).

@connor4312
Copy link
Author

Awesome, let me know if I can help with anything

@JoeEarly
Copy link

JoeEarly commented Feb 6, 2021

@znck @connor4312 Any movement on this issue? I can provide a project in early days that you can debug against

@andrewmackrodt
Copy link

andrewmackrodt commented Mar 2, 2021

From the VS Code perspective, the user might ask us to set a breakpoint in a Vue file. We know which file the breakpoint is attached to on disk, and we need to figure out which loaded file that breakpoint gets mapped to. However, because all these paths are quite similar and all (in the context of a generic web app) possibly the file we want, we end up putting the breakpoint in the wrong file and breaking in the incorrect place. And often the supporting scripts evaluate before the 'real' script does, so we can't, for example, wait and pick the best match from the possible candidates.

I've found a workaround which is to provide a custom function to webpack's output.devtoolModuleFilenameTemplate property which names component files according to which loaders are in use, i.e. if it is a vue file, the query is type=script and there are no additional loaders (e.g. ts-loader), then simply name the file Component.vue and breakpoints set in vscode work.

The following is only tested with vue-loader@16 in a typescript without babel project:

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('')
    }
  },
}

This assumes that a .vue file will only ever have one <script></script> element.

Additionally, for vue-loader@16 (vue 3 users), an extra step is required so that source maps have the correct line numbering (due to the way newline handling prior to <script></script> is handled. @vue/compiler-sfc requires pad: true but this isn't exposed anywhere by vue-loader so the function must be overridden until that functionality is added, i.e.

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

I explain the reasoning behind the latter in a bit more detail in vuejs/vue-cli#2897 (comment).

With those 2 changes, I'm able to set breakpoints in vscode and have execution pause at the correct place, including in async functions and timer callbacks, e.g. setTimeout and setInterval.

I've created a minimal ts + vue 3 project to test with: https://github.com/andrewmackrodt/vue3-ide-breakpoint-test

@1two3code
Copy link

@andrewmackrodt Thank you!

For anyone using vscode's debugger with this solution, you should also remember that to update the sourceMapPathOverrides in their launch config correspondingly.
As such:

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

@connor4312
Copy link
Author

@znck let me know what you think a good solution is. We've gotten continued reports of people running into this with VS Code and I remain more than happy to help get this fixed 🙂

@shameleo
Copy link

Beware that this code

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

leads to huge bugs with latest versions of vue-loader (currently 16.8.3, 17.0.0, maybe a bit earlier). The most amazing is completely wrong source parsing, when even your variables names are splitted into several parts and then webpack complains partial_var_name is unknown :). But maybe it just me

@uka17
Copy link

uka17 commented Jan 24, 2022

Well, spend 4 hours trying to resolve this vue-sfc-debug-typescript issue. Finally only this helped #11023 (comment)
In order to initially land breakpoint properly you also need to have:

      "sourceMapPathOverrides": {
        "webpack://src/*": "${webRoot}/*"
      }

@andrewmackrodt you are genius, respect and many thanks for work done!

@deisner
Copy link

deisner commented Mar 17, 2022

Thanks to everybody here, especially @andrewmackrodt -- I owe you a beer (or beverage of your choice). Your output.devtoolModuleFilenameTemplate did the trick. (I didn't need the @vue/compiler-sfcfix since I'm not using Vue 3).

Just a few notes to help anybody else in my position. I'm working on a containerized project with Vue 2, manually configured (i.e. without vue-cli), using docker-compose and HMR. Also:

  • vscode v1.65.2 (on macos)
  • vue@2.6.14
  • vue-loader@15.9.8
  • webpack@5.70.0

We have one repo, with the frontend in a subdirectory of the project root, and a package.json in that subdirectory, so
something like this:

project
├── backend-name
└── frontend-package-name
    ├── package.json
    └── src
        ├── App.vue
        └── components

When generating pathnames in the source map, vue-loader uses the package name (I think) at the root. After including the output.devtoolModuleFilenameTemplate, my source map pathnames look like this:

src://frontend-package-name/src/App.vue

As a result, my path override is just:

      "sourceMapPathOverrides": { 
        "src://*": "${webRoot}/*"
      }

I recommend right-clicking on a filename in Chrome devtools (in the Sources tab's directory tree) and using Copy link address to see what you need for your sourceMapPathOverrides. Without this information I would have been lost.

Also, my vscode debug config is setup to attach to an already running chrome, launched from the command line with the --remote-debugging-port=9222. My launch.json is then:

    {
      "name": "Attach to Chrome",
      "port": 9222,
      "url": "http://localhost/*",
      "request": "attach",
      "type": "pwa-chrome",
      "sourceMapPathOverrides": {
        "src://*": "${webRoot}/*"
      }
    },

One final note: My webpack devtool is set to eval-cheap-module-source-map. This is probably something you don't have to worry about when you're using vue-cli, but I thought I'd mention it.

@dev-vinicius-andrade
Copy link

dev-vinicius-andrade commented Aug 5, 2022

Hello All,
I've a project in vue 2.6.14, and I made it work only fully removing the propery name from package.json and package-lock.json;
There's a question i answered in stackoverflow with details

@johnlwebb3
Copy link

johnlwebb3 commented Sep 14, 2023

Connor Peet indicated I was experiencing this issue when he researched why I cannot set breakpoints in VS Code for my Vue3 app.

[https://github.com/microsoft/vscode-js-debug/issues/1799]

I just wanted to sound off here and say I spent many hours over several days trying to get breakpoint setting to work in VS Code. It gave me a pretty negative impression of the IDE. I'm glad I stuck with it and submitted the issue to you guys and learned that VS Code did indeed have an issue.

I subsequently have migrated my project to Vite and as a result, no longer have the breakpoint setting issue. Apparently Vue3 with Vite does not suffer from the same issue. It took a few hours to migrate my project but it was worth it. Vite is fast!

John

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests