Skip to content

Commit

Permalink
improve loading of async stack frames
Browse files Browse the repository at this point in the history
  • Loading branch information
gaojude committed Feb 6, 2025
1 parent ae5026a commit ff1a05b
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 132 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@ export const Default: Story = {
id: 0,
runtime: true,
error: new Error('First error message'),
frames: [],
frames: () => Promise.resolve([]),
},
{
id: 1,
runtime: true,
error: new Error('Second error message'),
frames: [],
frames: () => Promise.resolve([]),
},
{
id: 2,
runtime: true,
error: new Error('Third error message'),
frames: [],
frames: () => Promise.resolve([]),
},
],
activeIdx: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@ const mockErrors = [
id: 1,
runtime: true as const,
error: new Error('First error'),
frames: [],
frames: () => Promise.resolve([]),
},
{
id: 2,
runtime: true as const,
error: new Error('Second error'),
frames: [],
frames: () => Promise.resolve([]),
},
{
id: 3,
runtime: true as const,
error: new Error('Third error'),
frames: [],
frames: () => Promise.resolve([]),
},
]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,74 +74,78 @@ const readyErrors: ReadyRuntimeError[] = [
id: 1,
runtime: true,
error: new Error('First error message'),
frames: [
frame,
{
...frame,
originalStackFrame: {
...frame.originalStackFrame,
methodName: 'ParentComponent',
lineNumber: 5,
frames: () =>
Promise.resolve([
frame,
{
...frame,
originalStackFrame: {
...frame.originalStackFrame,
methodName: 'ParentComponent',
lineNumber: 5,
},
},
},
{
...frame,
originalStackFrame: {
...frame.originalStackFrame,
methodName: 'GrandparentComponent',
lineNumber: 1,
{
...frame,
originalStackFrame: {
...frame.originalStackFrame,
methodName: 'GrandparentComponent',
lineNumber: 1,
},
},
},
...Array(20).fill(ignoredFrame),
],
...Array(20).fill(ignoredFrame),
]),
},
{
id: 2,
runtime: true,
error: new Error('Second error message'),
frames: [
{
error: true,
reason: 'Second error message',
external: false,
ignored: false,
sourceStackFrame,
originalStackFrame,
originalCodeFrame: originalCodeFrame('Second error message'),
},
],
frames: () =>
Promise.resolve([
{
error: true,
reason: 'Second error message',
external: false,
ignored: false,
sourceStackFrame,
originalStackFrame,
originalCodeFrame: originalCodeFrame('Second error message'),
},
]),
},
{
id: 3,
runtime: true,
error: new Error('Third error message'),
frames: [
{
error: true,
reason: 'Third error message',
external: false,
ignored: false,
sourceStackFrame,
originalStackFrame,
originalCodeFrame: originalCodeFrame('Third error message'),
},
],
frames: () =>
Promise.resolve([
{
error: true,
reason: 'Third error message',
external: false,
ignored: false,
sourceStackFrame,
originalStackFrame,
originalCodeFrame: originalCodeFrame('Third error message'),
},
]),
},
{
id: 4,
runtime: true,
error: new Error('Fourth error message'),
frames: [
{
error: true,
reason: 'Fourth error message',
external: false,
ignored: false,
sourceStackFrame,
originalStackFrame,
originalCodeFrame: originalCodeFrame('Fourth error message'),
},
],
frames: () =>
Promise.resolve([
{
error: true,
reason: 'Fourth error message',
external: false,
ignored: false,
sourceStackFrame,
originalStackFrame,
originalCodeFrame: originalCodeFrame('Fourth error message'),
},
]),
},
]

Expand Down Expand Up @@ -205,21 +209,22 @@ export const WithHydrationWarning: Story = {
},
],
}),
frames: [
{
error: true,
reason: 'First error message',
external: false,
ignored: false,
sourceStackFrame: {
file: 'app/page.tsx',
methodName: 'Home',
arguments: [],
lineNumber: 10,
column: 5,
frames: () =>
Promise.resolve([
{
error: true,
reason: 'First error message',
external: false,
ignored: false,
sourceStackFrame: {
file: 'app/page.tsx',
methodName: 'Home',
arguments: [],
lineNumber: 10,
column: 5,
},
},
},
],
]),
},
],
debugInfo: { devtoolsFrontendUrl: undefined },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useMemo, useEffect, useRef } from 'react'
import { useState, useMemo, useEffect, useRef, Suspense } from 'react'
import type { DebugInfo } from '../../../types'
import { Overlay } from '../components/overlay'
import { noop as css } from '../helpers/noop-template'
Expand Down Expand Up @@ -192,11 +192,13 @@ export function Errors({
reactOutputComponentDiff={errorDetails.reactOutputComponentDiff}
/>
) : null}
<RuntimeError
key={activeError.id.toString()}
error={activeError}
dialogResizerRef={dialogResizerRef}
/>
<Suspense>
<RuntimeError
key={activeError.id.toString()}
error={activeError}
dialogResizerRef={dialogResizerRef}
/>
</Suspense>
</ErrorOverlayLayout>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo } from 'react'
import { use, useMemo } from 'react'
import { CodeFrame } from '../../components/code-frame/code-frame'
import { CallStack } from '../../components/errors/call-stack/call-stack'
import { noop as css } from '../../helpers/noop-template'
Expand All @@ -11,16 +11,18 @@ export type RuntimeErrorProps = {
}

export function RuntimeError({ error, dialogResizerRef }: RuntimeErrorProps) {
const frames = use(error.frames())

const firstFrame = useMemo(() => {
const firstFirstPartyFrameIndex = error.frames.findIndex(
const firstFirstPartyFrameIndex = frames.findIndex(
(entry) =>
!entry.ignored &&
Boolean(entry.originalCodeFrame) &&
Boolean(entry.originalStackFrame)
)

return error.frames[firstFirstPartyFrameIndex] ?? null
}, [error.frames])
return frames[firstFirstPartyFrameIndex] ?? null
}, [frames])

return (
<>
Expand All @@ -32,7 +34,7 @@ export function RuntimeError({ error, dialogResizerRef }: RuntimeErrorProps) {
)}

{error.frames.length > 0 && (
<CallStack dialogResizerRef={dialogResizerRef} frames={error.frames} />
<CallStack dialogResizerRef={dialogResizerRef} frames={frames} />
)}
</>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,25 +81,13 @@ export function useErrorHook({
if (nextError == null) {
return
}
let mounted = true

getErrorByType(nextError, isAppDir).then(
(resolved) => {
// We don't care if the desired error changed while we were resolving,
// thus we're not tracking it using a ref. Once the work has been done,
// we'll store it.
if (mounted) {
setLookups((m) => ({ ...m, [resolved.id]: resolved }))
}
},
() => {
// TODO: handle this, though an edge case
}
)
const resolved = getErrorByType(nextError, isAppDir)

return () => {
mounted = false
}
// We don't care if the desired error changed while we were resolving,
// thus we're not tracking it using a ref. Once the work has been done,
// we'll store it.
setLookups((m) => ({ ...m, [resolved.id]: resolved }))
}, [nextError, isAppDir])

return {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect, useMemo, useCallback } from 'react'
import { useState, useEffect, useMemo, useCallback, Suspense } from 'react'
import {
ACTION_UNHANDLED_ERROR,
ACTION_UNHANDLED_REJECTION,
Expand Down Expand Up @@ -162,25 +162,13 @@ export function Errors({
if (nextError == null) {
return
}
let mounted = true

getErrorByType(nextError, isAppDir).then(
(resolved) => {
// We don't care if the desired error changed while we were resolving,
// thus we're not tracking it using a ref. Once the work has been done,
// we'll store it.
if (mounted) {
setLookups((m) => ({ ...m, [resolved.id]: resolved }))
}
},
() => {
// TODO: handle this, though an edge case
}
)

return () => {
mounted = false
}
const resolved = getErrorByType(nextError, isAppDir)

// We don't care if the desired error changed while we were resolving,
// thus we're not tracking it using a ref. Once the work has been done,
// we'll store it.
setLookups((m) => ({ ...m, [resolved.id]: resolved }))
}, [nextError, isAppDir])

const [displayState, setDisplayState] =
Expand Down Expand Up @@ -399,9 +387,14 @@ export function Errors({
</div>
) : undefined}
</DialogHeader>
<DialogBody className="nextjs-container-errors-body">
<RuntimeError key={activeError.id.toString()} error={activeError} />
</DialogBody>
<Suspense>
<DialogBody className="nextjs-container-errors-body">
<RuntimeError
key={activeError.id.toString()}
error={activeError}
/>
</DialogBody>
</Suspense>
</DialogContent>
</Dialog>
</Overlay>
Expand Down
Loading

0 comments on commit ff1a05b

Please sign in to comment.