Repository navigation
Upgrade to WebAssembly version of source-map #98
Copy link
Copy link
Open
Labels
Description
source-map v0.7.x uses WebAssembly for a remarkably faster parsing of source maps. StackTraceGPS is compatible with it with only very minor changes.
Expected Behavior
StackTraceGPS uses WebAssembly to parse source maps.
Context
Parsing modern web app source maps on the client in JavaScript leads to an unusable user experience, locking up the browser for long enough that the user may suspect the app has crashed, and thus force-quits the browser before we could send the parsed error to our server.
Possible Solution
I'm currently enabling it as follows:
package.json:
{
// …
"resolutions": {
"stacktrace-gps@npm:3.1.2/source-map@npm:0.5.6": "npm:source-map@^0.7.6"
}
}./stacktrace-gps.ts:
import type * as StackFrame from "stackframe"
import type * as StackTrace from "stacktrace-js"
import { SourceMapConsumer } from "source-map"
import mappingsUrl from "source-map/lib/mappings.wasm?url"
import StackTraceGPS from "stacktrace-gps"
SourceMapConsumer.initialize({ "lib/mappings.wasm": mappingsUrl })
interface _StackTraceGPS extends StackTraceGPS {
sourceMapConsumerCache: {
[sourceMappingUrl: string]: SourceMapConsumer | Promise<SourceMapConsumer>
}
pinpoint(stackframe: StackFrame | StackTrace.StackFrame): Promise<StackFrame>
findFunctionName(
stackframe: StackFrame | StackTrace.StackFrame,
): Promise<StackFrame>
getMappedLocation(
stackframe: StackFrame | StackTrace.StackFrame,
): Promise<StackFrame>
[Symbol.dispose](): void
}
// polyfill for `Symbol.dispose`
Symbol.dispose ??= Symbol("Symbol.dispose")
class _StackTraceGPS extends StackTraceGPS {
[Symbol.dispose]() {
for (const sourceMapConsumerPromise of Object.values(
this.sourceMapConsumerCache,
)) {
Promise.resolve(sourceMapConsumerPromise).then((sourceMapConsumer) => {
return sourceMapConsumer.destroy()
})
}
}
}
export { _StackTraceGPS as StackTraceGPS }./report-error.ts:
import { StackTraceGPS } from "./stacktrace-gps.ts"
const report = async (error: Error) => {
using gps = new StackTraceGPS()
const stack = await Promise.all(
(await StackTrace.fromError(payload.error, { offline: true })).map(
async (stackframe) => {
try {
return await gps.pinpoint(stackframe)
} catch {
return stackframe
}
},
),
)
// report the error…
}For users that can't use using, a helper with function, modelled on source-map's SourceMapConsumer.with, or a destroy method could work well.
Reactions are currently unavailable
Activity