Skip to content

Commit

Permalink
refactor(middleware): leverages edge-runtime builtins to decorate err…
Browse files Browse the repository at this point in the history
…ors in dev (#37718)

### What's in there?

This is a followup of #37695.
For the dev server to clean stacktraces, we're decorating errors caught during code evaluation (`getServerSideProps` or middleware).
However, when these errors are asynchronously raised, we can't decorate them before processing them, leading to this fallback logic:

https://github.com/vercel/next.js/blob/bf7bf8217f0d3df37d740a0cebf2f140e9c518b6/packages/next/server/dev/next-dev-server.ts#L775-L779

Thanks to latest improvement of the edge-runtime in 1.1.0-beta.4, we can now catch unhandled rejection and uncaught exception, and decorate them.

### How to test?

Please reuse the existing tests who already covered these cases:
`pnpm testheadless --testPathPattern middleware-dev-errors`


Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com>
  • Loading branch information
feugy and ijjk committed Jun 17, 2022
1 parent e4b1fb0 commit c2b8006
Show file tree
Hide file tree
Showing 10 changed files with 648 additions and 608 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"@babel/plugin-proposal-object-rest-spread": "7.14.7",
"@babel/preset-flow": "7.14.5",
"@babel/preset-react": "7.14.5",
"@edge-runtime/jest-environment": "1.1.0-beta.2",
"@edge-runtime/jest-environment": "1.1.0-beta.6",
"@fullhuman/postcss-purgecss": "1.3.0",
"@mdx-js/loader": "0.18.0",
"@next/bundle-analyzer": "workspace:*",
Expand Down
1,094 changes: 557 additions & 537 deletions packages/next/compiled/@edge-runtime/primitives/index.js
100755 → 100644

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"name":"@edge-runtime/primitives","version":"1.1.0-beta.2","main":"./index.js","license":"MPLv2"}
{"name":"@edge-runtime/primitives","version":"1.1.0-beta.6","main":"./index.js","license":"MPLv2"}
2 changes: 1 addition & 1 deletion packages/next/compiled/edge-runtime/index.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions packages/next/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
"@babel/runtime": "7.15.4",
"@babel/traverse": "7.18.0",
"@babel/types": "7.18.0",
"@edge-runtime/primitives": "1.1.0-beta.2",
"@edge-runtime/primitives": "1.1.0-beta.6",
"@hapi/accept": "5.0.2",
"@napi-rs/cli": "2.4.4",
"@napi-rs/triples": "1.1.0",
Expand Down Expand Up @@ -194,7 +194,7 @@
"debug": "4.1.1",
"devalue": "2.0.1",
"domain-browser": "4.19.0",
"edge-runtime": "1.1.0-beta.2",
"edge-runtime": "1.1.0-beta.6",
"etag": "1.8.1",
"events": "3.3.0",
"find-cache-dir": "3.3.1",
Expand Down
6 changes: 1 addition & 5 deletions packages/next/server/dev/next-dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -770,12 +770,8 @@ export default class DevServer extends Server {
const src = getErrorSource(err)
if (src === 'edge-server') {
compilation = this.hotReloader?.edgeServerStats?.compilation
} else if (src === 'server') {
compilation = this.hotReloader?.serverStats?.compilation
} else {
compilation = frame.file!.includes('(middleware)')
? this.hotReloader?.edgeServerStats?.compilation
: this.hotReloader?.serverStats?.compilation
compilation = this.hotReloader?.serverStats?.compilation
}

const source = await getSourceById(
Expand Down
32 changes: 25 additions & 7 deletions packages/next/server/web/sandbox/context.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import type { Primitives } from 'next/dist/compiled/@edge-runtime/primitives'
import type { WasmBinding } from '../../../build/webpack/loaders/get-module-build-info'
import { getServerError } from 'next/dist/compiled/@next/react-dev-overlay/dist/middleware'
import {
decorateServerError,
getServerError,
} from 'next/dist/compiled/@next/react-dev-overlay/dist/middleware'
import { EDGE_UNSUPPORTED_NODE_APIS } from '../../../shared/lib/constants'
import { EdgeRuntime } from 'next/dist/compiled/edge-runtime'
import { readFileSync, promises as fs } from 'fs'
Expand Down Expand Up @@ -127,9 +130,12 @@ async function createModuleContext(options: ModuleContextOptions) {
function __next_webassembly_compile__(fn: Function) {
const key = fn.toString()
if (!warnedWasmCodegens.has(key)) {
const warning = new Error(
"Dynamic WASM code generation (e. g. 'WebAssembly.compile') not allowed in Middleware.\n" +
'Learn More: https://nextjs.org/docs/messages/middleware-dynamic-wasm-compilation'
const warning = getServerError(
new Error(
"Dynamic WASM code generation (e. g. 'WebAssembly.compile') not allowed in Middleware.\n" +
'Learn More: https://nextjs.org/docs/messages/middleware-dynamic-wasm-compilation'
),
'edge-server'
)
warning.name = 'DynamicWasmCodeGenerationWarning'
Error.captureStackTrace(warning, __next_webassembly_compile__)
Expand All @@ -153,9 +159,12 @@ async function createModuleContext(options: ModuleContextOptions) {

const key = fn.toString()
if (instantiatedFromBuffer && !warnedWasmCodegens.has(key)) {
const warning = new Error(
"Dynamic WASM code generation ('WebAssembly.instantiate' with a buffer parameter) not allowed in Middleware.\n" +
'Learn More: https://nextjs.org/docs/messages/middleware-dynamic-wasm-compilation'
const warning = getServerError(
new Error(
"Dynamic WASM code generation ('WebAssembly.instantiate' with a buffer parameter) not allowed in Middleware.\n" +
'Learn More: https://nextjs.org/docs/messages/middleware-dynamic-wasm-compilation'
),
'edge-server'
)
warning.name = 'DynamicWasmCodeGenerationWarning'
Error.captureStackTrace(warning, __next_webassembly_instantiate__)
Expand Down Expand Up @@ -228,6 +237,9 @@ async function createModuleContext(options: ModuleContextOptions) {
},
})

runtime.context.addEventListener('unhandledrejection', decorateUnhandledError)
runtime.context.addEventListener('error', decorateUnhandledError)

return {
runtime,
paths: new Map<string, string>(),
Expand Down Expand Up @@ -314,3 +326,9 @@ Learn more: https://nextjs.org/docs/api-reference/edge-runtime`)
warnedAlready.add(name)
}
}

function decorateUnhandledError(error: any) {
if (error instanceof Error) {
decorateServerError(error, 'edge-server')
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ export function getErrorSource(error: Error): 'server' | 'edge-server' | null {
return (error as any)[symbolError] || null
}

export function getServerError(
error: Error,
type: 'edge-server' | 'server'
): Error {
type ErrorType = 'edge-server' | 'server'

export function getServerError(error: Error, type: ErrorType): Error {
let n: Error
try {
throw new Error(error.message)
Expand Down Expand Up @@ -59,11 +58,15 @@ export function getServerError(
n.stack = error.stack
}

Object.defineProperty(n, symbolError, {
decorateServerError(n, type)
return n
}

export function decorateServerError(error: Error, type: ErrorType) {
Object.defineProperty(error, symbolError, {
writable: false,
enumerable: false,
configurable: false,
value: type,
})
return n
}
5 changes: 4 additions & 1 deletion packages/react-dev-overlay/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import { getRawSourceMap } from './internal/helpers/getRawSourceMap'
import { launchEditor } from './internal/helpers/launchEditor'

export { getErrorSource } from './internal/helpers/nodeStackFrames'
export { getServerError } from './internal/helpers/nodeStackFrames'
export {
decorateServerError,
getServerError,
} from './internal/helpers/nodeStackFrames'
export { parseStack } from './internal/helpers/parseStack'

export type OverlayMiddlewareOptions = {
Expand Down
94 changes: 47 additions & 47 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit c2b8006

Please sign in to comment.