vue
vue copied to clipboard
Generate distinct paths for supporting .vue files in sourcemaps
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.
Maybe @znck or @sodatea know more about where to do something here
This happens in vue-loader, right now I donβt have bandwidth to take it but I can guide if someone is interested.
I could probably find some time to help out. Code pointers or guidance would be appreciated π
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
In https://github.com/microsoft/vscode-js-debug/commit/2f3c93a08df0ceb09eda86251d0309d34bfb26ef 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.
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 :shrug:
@znck let me know what you think a good solution is. We've gotten continued reports of people running into this with VS Code π
Looking forward to the fix
It's been almost 10 months with no indication of it being fixed...
@znck I would appreciate your input here. This continues to be a pain point for Vue users on VS Code. (most recently in https://github.com/microsoft/vscode-js-debug/issues/754)
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).
Awesome, let me know if I can help with anything
@znck @connor4312 Any movement on this issue? I can provide a project in early days that you can debug against
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 https://github.com/vuejs/vue-cli/issues/2897#issuecomment-788952677.
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
@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/*"
}
@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 π
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
Well, spend 4 hours trying to resolve this vue-sfc-debug-typescript issue. Finally only this helped https://github.com/vuejs/vue/issues/11023#issuecomment-789137369 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!
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-sfc
fix 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)
- [email protected]
- [email protected]
- [email protected]
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.
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
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