microsoft / rushstack

Monorepo for tools developed by the Rush Stack community
https://rushstack.io/
Other
5.89k stars 595 forks source link

[heft] Update heft-webpack-basic-tutorial with a best practice for sourcemaps #2316

Open hbo-iecheruo opened 3 years ago

hbo-iecheruo commented 3 years ago

If you configure a project using the webpack basic tutorial

Is there a known webpack recipe for getting source maps that point to original sources and sources in dependent projects?

heft: v0.15.5

octogonz commented 3 years ago

The heft-webpack-basic-tutorial source maps actually don't work in Chrome. That sounds like a bug that needs to get fixed.

octogonz commented 3 years ago

there are no source map entries pointing at any dependencies in the mono-repo

To expand on this, suppose there is a web application example-app that depends on another Rush project example-lib. Heft's generated source maps look like this:

example-app/lib/MyApp.js.map

{"version":3,"file":"MyApp.js","sourceRoot":"","sources":["../src/MyApp.tsx"],"names":[],"mappings":"AAAA, ...

example-lib/lib/index.js.map

{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA, ...

When debugging in Chrome, the source file paths are displayed like this:

In other words, the .. folders are combined weirdly by Chrome. Firefox's folder tree simply overlays them, showing index.ts and MyApp.tsx as being under the same src folder.

This isn't a good experience. Instead there should be some way to map displayed paths to indicate the Rush project, something like:

Can webpack.config.js perform this translation? Or should Heft inject something into its emitted .js.map files, before Webpack processes them?

CC @iclanton

octogonz commented 3 years ago

Today we found that setting "sourceRoot" in tsconfig.json can be used to customize the path that is displayed in the debugger. For example, if I set "sourceRoot": "example-app" for one project, and "sourceRoot": "example-lib" for the other project, then they are properly separated in the treeview. It seems to work with both Chrome and Firefox, and even works for debugging Jest tests.

I'm not sure why this doesn't interfere with finding the real paths, though. 🤔 Maybe "inlineSources": true helps that somehow?

The TypeScript docs say that "sourceRoot" gets copied verbatim into the .js.map file. So if tinkering with "sourceRoot" is a good solution, maybe the Heft plugin could inject the package name into that field somehow?

(Before we go too far down this rabbit hole though, it might be useful to look at some other Webpack monorepos and see how they handled it.)

iclanton commented 3 years ago

Can webpack.config.js perform this translation?

Good question. I'm not sure. I think we may need to put together (or find) a Webpack plugin to do this.

Or should Heft inject something into its emitted .js.map files, before Webpack processes them?

That may be the best option. If we inline sources into the sourcemaps and then tweak the source path, we may be able to trick Webpack's sourcemap processor into taking our generated (and not real) source path without actually looking for the source file on disk. Just tinkering with the sourceroot TSConfig option will be trivial in Heft if that's all we need to do.

@dmichon-msft may have some expertise here. I'll admit that I don't actually use sourcemaps because in the kinds of web code that I usually find myself debugging, the sourcemap is likely to hide the problem.

dmichon-msft commented 3 years ago

The solution we've used internally is, in tsconfig.json / compilerOptions:

"sourceRoot": "../src",
"inlineSources": true

This also assumes that your webpack config uses source-map-loader to read the TS->JS source maps and compose them.

Edit: In all honesty, this sounds like a bug in the source map compositing engine used by webpack; it ought to be resolving source map references relative to the where it loaded the source map from and restructuring them.

hbo-iecheruo commented 3 years ago

option. If we inline sources into the sourcemaps and then tweak the source path, we may be able to trick Webpack's sourcemap processor into taking our generated (and not real) source path without actually looking for the source file on disk. Just tinkering with the sourceroot TSConfig option will be trivial in Heft if that's all we need to do.

This helps but doesn't quite get me where I want to be.

My ultimate goal is a) supporting breakpoints in .ts(x) files at development time. b) supporting call stacks to .ts(x) files in production builds. c) (Stretch) Allowing both goals above to be satisfied transitively across dependencies inside the same monorepo

Inlining source maps will solve (b) but not (a) because an IDE like VSCode will not be able to associate break points in editor files with the candidate file paths in source maps

octogonz commented 3 years ago

BTW if the source maps misrepresent the file paths too much, then when you are debugging Node.js or Jest, the debugger will not be able to find the real .ts file path on disk. This causes VS Code to display the source file in a read-only "ghosted" window (with the incorrect file path). Breakpoints will not work if they are placed in the real source file, only in the ghosted window.

Whatever solution we come up with, we should check that it doesn't run into this problem.