error code should preserve after stitching (#75929)

This commit is contained in:
Jude Gao
2025-02-11 19:28:32 -05:00
committed by GitHub
parent 0ef83cd39e
commit 673f0a9647
5 changed files with 38 additions and 6 deletions
+4 -1
View File
@@ -4,7 +4,7 @@
This plugin transforms Next.js source code by:
- Rewriting `new Error` or `Error` to include an additional property: `Object.defineProperty(new Error(...), "__NEXT_ERROR_CODE", { value: $code, enumerable: false })`. The `enumerable: false` ensures the error code won't show up in console logs while still being accessible for telemetry.
- Rewriting `new Error` or `Error` to include an additional property: `Object.defineProperty(new Error(...), "__NEXT_ERROR_CODE", { value: $code, enumerable: false, configurable: true })`. The `enumerable: false` ensures the error code won't show up in console logs while still being accessible for telemetry. The `configurable: true` ensures the error code can be overwritten, useful for transforming errors.
- This enables anonymous error code reporting for user feedback while keeping the message private.
## Error Code Mapping
@@ -36,6 +36,7 @@ This plugin transforms Next.js source code by:
- **Reason:** Pre-built artefacts simplify the build process (`pnpm build` runs without requiring `cargo`).
- **Rebuild Script Example:**
```bash
#!/usr/bin/env bash
set -e
@@ -46,3 +47,5 @@ This plugin transforms Next.js source code by:
mv "$NEXT_JS_ROOT/crates/next-error-code-swc-plugin/target/wasm32-wasip1/release/next_error_code_swc_plugin.wasm" "$NEXT_JS_ROOT/packages/next/"
echo "✨ Successfully built and moved WASM plugin! 🚀"
```
- **Testing:** Navigate to `/crates/next-error-code-swc-plugin` and run `cargo test` to test the plugin
+17 -5
View File
@@ -223,6 +223,13 @@ impl VisitMut for TransformVisitor {
value: false,
}))),
}))),
PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
key: PropName::Ident("configurable".into()),
value: Box::new(Expr::Lit(Lit::Bool(Bool {
span: new_error_expr.span,
value: true,
}))),
}))),
],
})), // { value: "$code", enumerable: false }
},
@@ -303,21 +310,24 @@ async function fetchUserData(userId) {
if (!response.ok) {
throw Object.defineProperty(new Error(`Failed to fetch user ${userId}: ${response.statusText}`), "__NEXT_ERROR_CODE", {
value: "E1",
enumerable: false
enumerable: false,
configurable: true
});
}
return await response.json();
} catch (err) {
throw Object.defineProperty(new Error(`Request failed: ${err.message}`), "__NEXT_ERROR_CODE", {
value: "E2",
enumerable: false
enumerable: false,
configurable: true
});
}
}
function test1() {
throw Object.defineProperty(new Error("Generic error"), "__NEXT_ERROR_CODE", {
value: "E3",
enumerable: false
enumerable: false,
configurable: true
});
}
function test2() {
@@ -326,14 +336,16 @@ function test2() {
function test3() {
throw Object.defineProperty(new Error("Generic error"), "__NEXT_ERROR_CODE", {
value: "E3",
enumerable: false
enumerable: false,
configurable: true
});
}
function test4() {
throw new Error();
throw Object.defineProperty(new Error("Pattern should define hostname but found\n" + JSON.stringify(pattern)), "__NEXT_ERROR_CODE", {
value: "E5",
enumerable: false
enumerable: false,
configurable: true
});
}
"#
Binary file not shown.
@@ -1,5 +1,6 @@
import React from 'react'
import isError from '../../../lib/is-error'
import { copyNextErrorCode } from '../../../lib/error-telemetry-utils'
const REACT_ERROR_STACK_BOTTOM_FRAME = 'react-stack-bottom-frame'
const REACT_ERROR_STACK_BOTTOM_FRAME_REGEX = new RegExp(
@@ -25,6 +26,7 @@ export function getReactStitchedError<T = unknown>(err: T): Error | T {
const newError = new Error(originMessage)
// Copy all enumerable properties, e.g. digest
Object.assign(newError, err)
copyNextErrorCode(err, newError)
newError.stack = newStack
// Avoid duplicate overriding stack frames
@@ -22,6 +22,21 @@ export const createDigestWithErrorCode = (
return originalDigest
}
/**
* Copies the error code from one error to another by setting the __NEXT_ERROR_CODE property.
* This allows error codes to be preserved when wrapping or transforming errors.
*/
export const copyNextErrorCode = (source: unknown, target: unknown): void => {
const errorCode = extractNextErrorCode(source)
if (errorCode && typeof target === 'object' && target !== null) {
Object.defineProperty(target, '__NEXT_ERROR_CODE', {
value: errorCode,
enumerable: false,
configurable: true,
})
}
}
export const extractNextErrorCode = (error: unknown): string | undefined => {
if (
typeof error === 'object' &&