Debug IDs
Debug IDs tie a deployed bundle to the exact source map produced by the same build. At build time, @tracewayapp/bundler-plugin embeds a unique 128-bit ID (UUID format) into both the bundle and its .map file. At runtime the SDK reports the IDs of the bundles involved in each exception, and the backend uses them to pick the matching map: content-addressed, with no dependence on filenames, URLs, versions, or deploy timing.
This is the same mechanism Sentry pioneered and the ECMA-426 source map specification's Debug ID proposal (opens in a new tab) standardizes: a debugId field in the source map JSON and a //# debugId=<uuid> comment in the generated file. Traceway emits and consumes exactly that format, so artifacts are interoperable with other ECMA-426 tooling, and Traceway accepts artifacts produced by other debug-ID-aware tools (Sentry's bundler plugins, sentry-cli sourcemaps inject, Rollup's output.sourcemapDebugIds, Bun).
Without debug IDs, maps are matched by filename, which works with content-hashed bundle names but breaks down with stable names like app.js served from multiple concurrent deploys. See How Maps Are Matched.
Installation
npm install -D @tracewayapp/bundler-pluginVite
// vite.config.ts
import { defineConfig } from "vite";
import { tracewayDebugIds } from "@tracewayapp/bundler-plugin/vite";
export default defineConfig({
build: {
sourcemap: true,
},
plugins: [tracewayDebugIds()],
});Rollup
// rollup.config.js
import { tracewayDebugIds } from "@tracewayapp/bundler-plugin/rollup";
export default {
output: {
sourcemap: true,
},
plugins: [tracewayDebugIds()],
};webpack
// webpack.config.js
const {
TracewayDebugIdsWebpackPlugin,
} = require("@tracewayapp/bundler-plugin/webpack");
module.exports = {
devtool: "source-map",
plugins: [new TracewayDebugIdsWebpackPlugin()],
};Requires webpack 5.
Upload As Usual
The upload step is unchanged; the backend detects debug IDs in the uploaded files automatically:
npx traceway-sourcemaps \
--url https://your-traceway-instance.com \
--token your-sourcemap-token \
--directory ./distHow It Works
For every emitted JS chunk, the plugin:
- Derives a deterministic debug ID from the chunk's content (a SHA-256 hash formatted as a UUID), so rebuilding identical input yields the identical ID.
- Injects a small self-executing snippet at the top of the chunk (after any shebang and
"use strict"directive) that registers the ID in a_tracewayDebugIdsglobal, keyed by the stack of a synthetic error, which is how the SDK later discovers the runtime URL each bundle was loaded from. Source maps are adjusted for the insertion, so resolved positions are unaffected. - Appends a
//# debugId=<uuid>comment to the chunk, above the//# sourceMappingURL=comment. - Writes the ID into the source map JSON as
debugId(anddebug_idfor compatibility with Sentry-era tooling).
When an exception is captured, the SDK reads _tracewayDebugIds (and _sentryDebugIds, so bundles processed by Sentry tooling work too), maps each bundle filename in the stack trace to its debug ID, and sends that mapping with the exception. At ingest, the backend resolves each frame against the map uploaded with that exact debug ID, falling back to filename matching when no debug-ID artifact exists. Uploaded artifacts carrying a debug ID are stored under both their filename and a by-debug-id/ key, so old SDK versions and frames without IDs keep resolving.
Node and OpenTelemetry
Debug IDs work the same way for server-side bundles: the injected snippet detects globalThis/global, and Node stack traces carry the bundle path just like browser ones. If you bundle a Node service with Vite, Rollup, or webpack, the same plugin applies.
OpenTelemetry currently has no debug ID convention: there is no semantic-conventions attribute for source map linkage, and the JS SDKs do not collect anything like it. Traceway therefore carries debug IDs in its own report payload (a debugIds filename-to-ID map on each exception). Because the build artifacts follow the ECMA-426 format rather than anything Traceway-specific, a future Node/OTel integration can adopt the same uploads without rebuilding anything.
Interoperability Notes
- Rollup/Vite native IDs: Rollup's
output.sourcemapDebugIds: trueemits the comment and map field but no runtime registry, so the SDK cannot report IDs at runtime from that alone. The Traceway plugin is a superset; you do not need both. - Sentry tooling: bundles processed by
@sentry/vite-plugin,@sentry/webpack-plugin, orsentry-cli sourcemaps injectregister_sentryDebugIds, which the Traceway SDK also reads. Maps carrying only the legacydebug_idfield are accepted on upload. - esbuild: the Traceway plugin does not cover esbuild, and esbuild emits no debug IDs natively. Bundles processed by
@sentry/esbuild-plugin(or a post-buildsentry-cli sourcemaps inject) get_sentryDebugIdsinjected, which the SDK reads as described above, so esbuild apps can still get exact debug-ID matching through that tooling; without it they fall back to filename matching.
Next Steps
- Source Maps: the upload CLI
- Symbolication: the full resolution pipeline