Better debugging for JS Compute apps: Stack traces and intermediate files in JavaScript SDK 3.37

If you’ve been building on Fastly Compute with JavaScript, you know the power of writing edge logic in a familiar web-style environment — but debugging hasn’t always been as smooth as it could be. When something throws an error in your handler, you might see just the name of the error, and even if you did get a stack trace, the line numbers often didn’t match anything in your actual source files.

Error while running request handler: foo
Stack:
  handler/result<@fastly:app.js:23:11
  buildResult@fastly:app.js:18:13
  handler@fastly:app.js:22:24
  @fastly:app.js:16:48

Example of error thrown without useful stack info

With the release of Fastly Compute JavaScript SDK 3.37, that changes. This version introduces source-mapped stack traces and an option to dump intermediate build files, making it meaningfully easier to diagnose and fix issues in your application.

What’s new

  • Stack tracing (--enable-stack-traces)
    When you enable this flag during your build, runtime errors will now include file/line/column numbers mapped back to your original source, surviving even through other bundlers and TypeScript.

    Unhandled error while running request handler
    TypeError: foo
      at (anonymous function) (src/index.ts:10:11)
          7 | 
          8 | async function handler(_e: FetchEvent) {
          9 |   const result = await buildResult(() => {
    >    10 |     throw new TypeError('foo');
                        ^
         11 |   });
         12 |   result.headers.set('content-type', 'text/plain');
         13 |   return result;
      at buildResult (src/index.ts:4:13)
      at handler (src/index.ts:9:24)
      at src/index.ts:1:46
    

    Example of useful stack info and code excerpt, enabled by --enable-stack-traces

    Note: Enabling stack traces implicitly includes a copy of your sources in your Wasm package. This is what allows the stack trace to show helpful nearby code, but it also means your pre-compilation source files are uploaded to Fastly’s servers.
    If you do not want to include your source in the deployed Wasm, you should also pass --exclude-sources for your production builds.

  • Source exclusion (--exclude-sources)
    Use this if you want stack tracing but prefer not to embed your raw sources. You’ll still get mapped line numbers, but without a code excerpt.

  • Intermediate file output (--debug-intermediate-files <dir>)
    When compiling your source code, the JavaScript SDK runs a few passes of processing on it.
    With this flag, you can dump these intermediate artifacts into a directory of your choice: the bundled JS, transformed code, and the final form that gets compiled into Wasm. It’s useful when debugging build-pipeline behavior or verifying exactly what the runtime is seeing.

If you’re curious about how this feature was implemented, the full discussion and code are here:

Why this matters

  • Faster iteration and debugging at the edge and locally
    Instead of deploying just to discover that “some error happened” with no useful stack info, you now see exactly which file and which line threw the error. Stack traces now point to your actual source lines, not the bundled/transpiled output.
  • More confidence with complex builds
    Frameworks, TypeScript, and multi-module apps all benefit from accurate stack mapping.
  • Visibility into the build pipeline
    Intermediate file dumps make it straightforward to diagnose issues that stem from bundling, transforms, loaders, or accidental code changes introduced by tooling.

Getting started

Here’s how you might set up the "build" script in your package.json:

{
  "scripts": {
    "build": "js-compute-runtime src/index.ts bin/main.wasm --enable-stack-traces"
  }
}

If you prefer not to upload your source code with the Wasm bundle, also add --exclude-sources.
Run npm run build as usual, then deploy or test locally. If an uncaught error is thrown in top level code or in the handler, you’ll now get a readable, source-mapped stack trace.

If you are catching an error in your own code, you’ll want to call mapError() (details) on it to get the mapped information.

import { mapError } from 'fastly:experimental';

try {
  // .. code that may throw
} catch(err) {
  // display error with mapped stack
  console.error(mapError(err).join('\n'));
}

Looking ahead

This release is another step in making JS development on Compute more ergonomic and transparent. If you try it out and have feedback, ideas, or issues, feel free to start a discussion here on the community forum or open an issue in the GitHub repo. We’d love to hear what you think.

5 Likes