-
Notifications
You must be signed in to change notification settings - Fork 27.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
319 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
import type { NextConfig } from '../server/config-shared' | ||
|
||
export function needsExperimentalReact(config: NextConfig) { | ||
const { ppr, taint, dynamicIO, reactOwnerStack } = config.experimental || {} | ||
return Boolean(ppr || taint || dynamicIO || reactOwnerStack) | ||
const { ppr, taint, reactOwnerStack } = config.experimental || {} | ||
return Boolean(ppr || taint || reactOwnerStack) | ||
} |
44 changes: 44 additions & 0 deletions
44
test/development/app-dir/stitching-errors/app/browser/caught/page.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
'use client' | ||
|
||
import { Component } from 'react' | ||
|
||
class MyErrorBoundary extends Component { | ||
static getDerivedStateFromError(error) { | ||
return { error } | ||
} | ||
|
||
state = { error: null } | ||
|
||
render() { | ||
if (this.state.error) { | ||
return 'failed' | ||
} | ||
return this.props.children | ||
} | ||
} | ||
|
||
function Inner() { | ||
return ( | ||
<MyErrorBoundary> | ||
<Thrower /> | ||
</MyErrorBoundary> | ||
) | ||
} | ||
|
||
function Thrower() { | ||
useErrorHook() | ||
} | ||
|
||
function useThrowError() { | ||
if (typeof window !== 'undefined') { | ||
throw new Error('browser error') | ||
} | ||
} | ||
|
||
function useErrorHook() { | ||
useThrowError() | ||
} | ||
|
||
export default function Page() { | ||
return <Inner /> | ||
} |
16 changes: 16 additions & 0 deletions
16
test/development/app-dir/stitching-errors/app/browser/uncaught/page.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
'use client' | ||
|
||
function useThrowError() { | ||
if (typeof window !== 'undefined') { | ||
throw new Error('browser error') | ||
} | ||
} | ||
|
||
function useErrorHook() { | ||
useThrowError() | ||
} | ||
|
||
export default function Page() { | ||
useErrorHook() | ||
return <p>hello world</p> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export default function Root({ children }) { | ||
return ( | ||
<html> | ||
<body>{children}</body> | ||
</html> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
'use client' | ||
|
||
function useThrowError() { | ||
throw new Error('ssr error') | ||
} | ||
|
||
function useErrorHook() { | ||
useThrowError() | ||
} | ||
|
||
export default function Page() { | ||
useErrorHook() | ||
return <p>hello world</p> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
/** | ||
* @type {import('next').NextConfig} | ||
*/ | ||
const nextConfig = { | ||
experimental: { | ||
reactOwnerStack: true, | ||
}, | ||
} | ||
|
||
module.exports = nextConfig |
194 changes: 194 additions & 0 deletions
194
test/development/app-dir/stitching-errors/stitching-errors.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
import { nextTestSetup } from 'e2e-utils' | ||
import { assertHasRedbox, assertNoRedbox } from 'next-test-utils' | ||
|
||
// Remove the location `()` part in every line of stack trace; | ||
// Remove the leading spaces in every line of stack trace; | ||
// Remove the trailing spaces in every line of stack trace; | ||
function normalizeStackTrace(trace: string) { | ||
return trace | ||
.replace(/\(.*\)/g, '') | ||
.replace(/^\s+/gm, '') | ||
.trim() | ||
} | ||
|
||
async function getStackFramesContent(browser) { | ||
const stackFrameElements = await browser.elementsByCss( | ||
'[data-nextjs-call-stack-frame]' | ||
) | ||
const stackFramesContent = ( | ||
await Promise.all(stackFrameElements.map((f) => f.innerText())) | ||
) | ||
.filter(Boolean) | ||
.join('\n') | ||
|
||
return normalizeStackTrace(stackFramesContent) | ||
} | ||
|
||
// Remove leading spaces in every line of stack trace | ||
|
||
describe('stitching errors', () => { | ||
const { next } = nextTestSetup({ | ||
files: __dirname, | ||
}) | ||
|
||
it('should log stitched error for browser uncaught errors', async () => { | ||
const browser = await next.browser('/browser/uncaught') | ||
|
||
await assertHasRedbox(browser) | ||
|
||
const stackFramesContent = await getStackFramesContent(browser) | ||
if (process.env.TURBOPACK) { | ||
expect(stackFramesContent).toMatchInlineSnapshot(` | ||
"useErrorHook | ||
app/browser/uncaught/page.js | ||
Page | ||
app/browser/uncaught/page.js" | ||
`) | ||
} else { | ||
expect(stackFramesContent).toMatchInlineSnapshot(` | ||
"useThrowError | ||
app/browser/uncaught/page.js | ||
useErrorHook | ||
app/browser/uncaught/page.js | ||
ReactDevOverlay | ||
../src/client/components/react-dev-overlay/app/hot-reloader-client.tsx | ||
assetPrefix | ||
../src/client/components/app-router.tsx | ||
actionQueue | ||
../src/client/components/app-router.tsx | ||
AppRouter | ||
../src/client/app-index.tsx" | ||
`) | ||
} | ||
|
||
const logs = await browser.log() | ||
const errorLog = logs.find((log) => { | ||
return log.message.includes('Error: browser error') | ||
}).message | ||
|
||
if (process.env.TURBOPACK) { | ||
expect(normalizeStackTrace(errorLog)).toMatchInlineSnapshot(` | ||
"Error: browser error | ||
at useThrowError | ||
at useErrorHook | ||
at Page | ||
at react-stack-bottom-frame | ||
at renderWithHooks | ||
at updateFunctionComponent | ||
at beginWork | ||
at runWithFiberInDEV | ||
at performUnitOfWork | ||
at workLoopSync | ||
at renderRootSync | ||
at recoverFromConcurrentError | ||
at performConcurrentWorkOnRoot | ||
at MessagePort.performWorkUntilDeadline | ||
The above error occurred in the <Page> component. It was handled by the <ReactDevOverlay> error boundary." | ||
`) | ||
} else { | ||
expect(normalizeStackTrace(errorLog)).toMatchInlineSnapshot(` | ||
"Error: browser error | ||
at useThrowError | ||
at useErrorHook | ||
at Page | ||
at react-stack-bottom-frame | ||
at renderWithHooks | ||
at updateFunctionComponent | ||
at beginWork | ||
at runWithFiberInDEV | ||
at performUnitOfWork | ||
at workLoopSync | ||
at renderRootSync | ||
at recoverFromConcurrentError | ||
at performConcurrentWorkOnRoot | ||
at MessagePort.performWorkUntilDeadline | ||
The above error occurred in the <Page> component. It was handled by the <ReactDevOverlay> error boundary." | ||
`) | ||
} | ||
}) | ||
|
||
it('should log stitched error for browser caught errors', async () => { | ||
const browser = await next.browser('/browser/caught') | ||
|
||
await assertNoRedbox(browser) | ||
|
||
const logs = await browser.log() | ||
const errorLog = logs.find((log) => { | ||
return log.message.includes('Error: browser error') | ||
}).message | ||
|
||
expect(normalizeStackTrace(errorLog)).toMatchInlineSnapshot(` | ||
"Error: browser error | ||
at useThrowError | ||
at useErrorHook | ||
at Thrower | ||
at react-stack-bottom-frame | ||
at renderWithHooks | ||
at updateFunctionComponent | ||
at beginWork | ||
at runWithFiberInDEV | ||
at performUnitOfWork | ||
at workLoopSync | ||
at renderRootSync | ||
at recoverFromConcurrentError | ||
at performConcurrentWorkOnRoot | ||
at MessagePort.performWorkUntilDeadline | ||
The above error occurred in the <Thrower> component. It was handled by the <MyErrorBoundary> error boundary." | ||
`) | ||
}) | ||
|
||
it('should log stitched error for SSR errors', async () => { | ||
const browser = await next.browser('/ssr') | ||
|
||
await assertHasRedbox(browser) | ||
|
||
const stackFramesContent = await getStackFramesContent(browser) | ||
if (process.env.TURBOPACK) { | ||
expect(stackFramesContent).toMatchInlineSnapshot(` | ||
"useErrorHook | ||
app/ssr/page.js | ||
Page | ||
app/ssr/page.js" | ||
`) | ||
} else { | ||
expect(stackFramesContent).toMatchInlineSnapshot(` | ||
"useThrowError | ||
app/ssr/page.js | ||
useErrorHook | ||
app/ssr/page.js | ||
ReactDevOverlay | ||
../src/client/components/react-dev-overlay/app/hot-reloader-client.tsx | ||
assetPrefix | ||
../src/client/components/app-router.tsx | ||
actionQueue | ||
../src/client/components/app-router.tsx | ||
AppRouter | ||
../src/client/app-index.tsx" | ||
`) | ||
} | ||
|
||
const logs = await browser.log() | ||
const errorLog = logs.find((log) => { | ||
return log.message.includes('Error: ssr error') | ||
}).message | ||
|
||
expect(normalizeStackTrace(errorLog)).toMatchInlineSnapshot(` | ||
"Error: ssr error | ||
at useThrowError | ||
at useErrorHook | ||
at Page | ||
at react-stack-bottom-frame | ||
at renderWithHooks | ||
at updateFunctionComponent | ||
at beginWork | ||
at runWithFiberInDEV | ||
at performUnitOfWork | ||
at workLoopSync | ||
at renderRootSync | ||
at recoverFromConcurrentError | ||
at performConcurrentWorkOnRoot | ||
at MessagePort.performWorkUntilDeadline | ||
The above error occurred in the <Page> component. It was handled by the <ReactDevOverlay> error boundary." | ||
`) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "ES2017", | ||
"lib": ["dom", "dom.iterable", "esnext"], | ||
"allowJs": true, | ||
"skipLibCheck": true, | ||
"strict": false, | ||
"noEmit": true, | ||
"incremental": true, | ||
"module": "esnext", | ||
"esModuleInterop": true, | ||
"moduleResolution": "node", | ||
"resolveJsonModule": true, | ||
"isolatedModules": true, | ||
"jsx": "preserve", | ||
"plugins": [ | ||
{ | ||
"name": "next" | ||
} | ||
] | ||
}, | ||
"include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"], | ||
"exclude": ["node_modules"] | ||
} |