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

Debug IDs: Add implementors section #152

Merged
merged 5 commits into from
Nov 26, 2024
Merged
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 27 additions & 43 deletions proposals/debug-id.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,58 +138,42 @@ with the following variables:

Note that debuginfod usually does not use extensions on the path lookup syntax so the more natural filenames would just be `source` and `sourcemap`.

## Appendix C: Emulating Debug IDs in JavaScript
## Polyfills

In the absence of browser support for loading debug IDs of particular stack frames, code transformers can inject a snippet into _all_ individual generated JavaScript files to maintain a global dictionary that maps from resource URL to Debug ID:
For this proposal, we include a [repository for "polyfilling" Debug IDs](https://github.com/getsentry/javascript-debug-ids).
It includes an implementation of plugins for various popular build-tooling as well as an implementation for a runtime API to access Debug IDs.

```javascript
(function () {
var stack = new Error().stack; // stack is non-standard and may be undefined
var match = stack && stack.match(/(?:\bat |@)(.*?):\d+:\d+$/m);
if (match) {
var ids = (globalThis.__DEBUG_IDS__ = globalThis.__DEBUG_IDS__ || {});
ids[match[1]] = "<DEBUG_ID>";
}
})();
```

```javascript
function getDebugIdForUrl(url) {
return globalThis.__DEBUG_IDS__ && globalThis.__DEBUG_IDS__[url];
}
```
Note: While polyfilling is possible and is in wide production use already[^1], we have found a plethora of issues:
- Complexity in setup and compatibility
- Polyfills usually require nasty workarounds for build-tool quirks
- Build-tools often don't allow for modifying source-maps
- Injecting Debug IDs into transitive dependencies is error prone and in some cases ruins the entire polyfilling process
- The polyfills inflate bundle-size more than necessary
- Chicken-and-egg situations with [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity)

## Appendix D: Parsing Debug IDs
## Implementors

The following Python code shows how Debug IDs are to be extracted from generated JavaScript and source map files:
The following Source Map **Generators** have implemented Debug IDs as proposed:

```python
import re
import uuid
import json
- Rollup ([`output.sourcemapDebugIds` option](https://rollupjs.org/configuration-options/#output-sourcemapdebugids))
- Oxc ([`debug_id` API](https://docs.rs/oxc/latest/oxc/sourcemap/struct.JSONSourceMap.html#structfield.debug_id))
- Expo ([Injected by default](https://docs.expo.dev/versions/latest/config/metro/#source-map-debug-id))
- Rolldown ([`output.sourcemapDebugIds` option](https://github.com/rolldown/rolldown/pull/2516))

The following Source Map **Consumers/Debuggers** have implemented Debug IDs:

_debug_id_re = re.compile(r'^//# debugId=(.*)')
- Sentry.io ([Docs](https://docs.sentry.io/platforms/javascript/sourcemaps/troubleshooting_js/artifact-bundles/#artifact-bundles))

The following implementations are work-in-progress:
- **Generator:** Webpack ([PR to extend `devtool` option](https://github.com/webpack/webpack/pull/18947))
- **Generator:** Turbopack (underlying `sourcemap` Rust crate has been updated)
- **Consumer:** V8 ([CL to extend `Error.prepareStackTrace`](https://chromium-review.googlesource.com/c/v8/v8/+/5979833), is awaiting for proposal to reach stage 3)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not have a work-in-progress section, since it can easily get out of date.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the section 👍


def normalize_debug_id(id):
try:
return uuid.UUID(id)
except ValueError:
return None
## Questions

- How should the `//# debugId=...` comment be parsed by consuming tools and JavaScript engines?
- How does the `//# debugId=...` comment interact with the `//# sourceMappingURL=...` comment?

def debug_id_from_generated_javascript(source):
for line in source.splitlines()[::-5]:
match = _debug_id_re.index(line)
if match is not None:
debug_id = normalize_debug_id(match.group(1))
if debug_id is not None:
return debug_id
---


def debug_id_from_source_map(source):
source_map = json.loads(source)
if "debugId" in source_map:
return normalize_debug_id(source_map["debugId"])
```
[^1]: Sentry.io is using the polyfills to enable its users to inject Debug IDs into generated code and Source Maps and is processing multiple hundreds of millions of artifacts with Debug IDs a month. Debug IDs in this very limited form have anecdotally worked out really well.
Loading