Skip to content

Commit

Permalink
fix global-error styles
Browse files Browse the repository at this point in the history
  • Loading branch information
gaojude committed Oct 27, 2024
1 parent 3265c95 commit 3356b1c
Show file tree
Hide file tree
Showing 12 changed files with 108 additions and 10 deletions.
4 changes: 3 additions & 1 deletion crates/next-core/src/app_page_loader_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ impl AppPageLoaderTreeBuilder {
page,
default,
error,
global_error: _,
global_error,
layout,
loading,
template,
Expand Down Expand Up @@ -344,6 +344,8 @@ impl AppPageLoaderTreeBuilder {
.await?;
self.write_modules_entry(AppDirModuleType::DefaultPage, *default)
.await?;
self.write_modules_entry(AppDirModuleType::GlobalError, *global_error)
.await?;

let modules_code = replace(&mut self.loader_tree_code, temp_loader_tree_code);

Expand Down
2 changes: 2 additions & 0 deletions crates/next-core/src/base_loader_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub enum AppDirModuleType {
Loading,
Template,
NotFound,
GlobalError,
}

impl AppDirModuleType {
Expand All @@ -40,6 +41,7 @@ impl AppDirModuleType {
AppDirModuleType::Loading => "loading",
AppDirModuleType::Template => "template",
AppDirModuleType::NotFound => "not-found",
AppDirModuleType::GlobalError => "global-error",
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const FILE_TYPES = {
error: 'error',
loading: 'loading',
'not-found': 'not-found',
'global-error': 'global-error',
} as const

const GLOBAL_ERROR_FILE_TYPE = 'global-error'
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/client/app-index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ function ServerRoot(): React.ReactNode {
const router = (
<AppRouter
actionQueue={actionQueue}
globalErrorComponent={initialRSCPayload.G}
globalErrorComponentAndStyles={initialRSCPayload.G}
assetPrefix={initialRSCPayload.p}
/>
)
Expand Down
9 changes: 6 additions & 3 deletions packages/next/src/client/components/app-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -622,17 +622,20 @@ function Router({

export default function AppRouter({
actionQueue,
globalErrorComponent,
globalErrorComponentAndStyles: [globalErrorComponent, globalErrorStyles],
assetPrefix,
}: {
actionQueue: AppRouterActionQueue
globalErrorComponent: ErrorComponent
globalErrorComponentAndStyles: [ErrorComponent, React.ReactNode | undefined]
assetPrefix: string
}) {
useNavFailureHandler()

return (
<ErrorBoundary errorComponent={globalErrorComponent}>
<ErrorBoundary
errorComponent={globalErrorComponent}
errorStyles={globalErrorStyles}
>
<Router actionQueue={actionQueue} assetPrefix={assetPrefix} />
</ErrorBoundary>
)
Expand Down
37 changes: 33 additions & 4 deletions packages/next/src/server/app-render/app-render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ import { InvariantError } from '../../shared/lib/invariant-error'

import './clean-async-snapshot.external'
import { INFINITE_CACHE } from '../../lib/constants'
import { createComponentStylesAndScripts } from './create-component-styles-and-scripts'
import { parseLoaderTree } from './parse-loader-tree'

export type GetDynamicParamFromSegment = (
// [slug] / [[slug]] / [...slug]
Expand Down Expand Up @@ -724,6 +726,8 @@ async function getRSCPayload(
</React.Fragment>
)

const globalErrorStyles = await getGlobalErrorStyles(tree, ctx)

return {
// See the comment above the `Preloads` component (below) for why this is part of the payload
P: <Preloads preloadCallbacks={preloadCallbacks} />,
Expand All @@ -733,7 +737,7 @@ async function getRSCPayload(
i: !!couldBeIntercepted,
f: [[initialTree, seedData, initialHead]],
m: missingSlots,
G: GlobalError,
G: [GlobalError, globalErrorStyles],
s: typeof ctx.renderOpts.postponed === 'string',
S: workStore.isStaticGeneration,
}
Expand Down Expand Up @@ -818,14 +822,16 @@ async function getErrorRSCPayload(
null,
]

const globalErrorStyles = await getGlobalErrorStyles(tree, ctx)

return {
b: ctx.renderOpts.buildId,
p: ctx.assetPrefix,
c: prepareInitialCanonicalUrl(url),
m: undefined,
i: false,
f: [[initialTree, initialSeedData, initialHead]],
G: GlobalError,
G: [GlobalError, globalErrorStyles],
s: typeof ctx.renderOpts.postponed === 'string',
S: workStore.isStaticGeneration,
} satisfies InitialRSCPayload
Expand Down Expand Up @@ -882,7 +888,7 @@ function App<T>({
<ServerInsertedHTMLProvider>
<AppRouter
actionQueue={actionQueue}
globalErrorComponent={response.G}
globalErrorComponentAndStyles={response.G}
assetPrefix={response.p}
/>
</ServerInsertedHTMLProvider>
Expand Down Expand Up @@ -931,7 +937,7 @@ function AppWithoutContext<T>({
return (
<AppRouter
actionQueue={actionQueue}
globalErrorComponent={response.G}
globalErrorComponentAndStyles={response.G}
assetPrefix={response.p}
/>
)
Expand Down Expand Up @@ -3814,3 +3820,26 @@ export async function warmFlightResponse(
chunkListeners.push(r)
})
}

const getGlobalErrorStyles = async (
tree: LoaderTree,
ctx: AppRenderContext
): Promise<React.ReactNode | undefined> => {
const {
modules: { 'global-error': globalErrorModule },
} = parseLoaderTree(tree)

let globalErrorStyles
if (globalErrorModule) {
const [, styles] = await createComponentStylesAndScripts({
ctx,
filePath: globalErrorModule[1],
getComponent: globalErrorModule[0],
injectedCSS: new Set(),
injectedJS: new Set(),
})
globalErrorStyles = styles
}

return globalErrorStyles
}
2 changes: 1 addition & 1 deletion packages/next/src/server/app-render/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ export type InitialRSCPayload = {
/** missingSlots */
m: Set<string> | undefined
/** GlobalError */
G: React.ComponentType<any>
G: [React.ComponentType<any>, React.ReactNode | undefined]
/** postponed */
s: boolean
/** prerendered */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use client'

import './global.css'

export default function GlobalError() {
return (
<html>
<body>
<h2>Error!</h2>
</body>
</html>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
h2 {
color: yellow;
}
12 changes: 12 additions & 0 deletions test/e2e/app-dir/global-error/with-style-import/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// to avoid bailing out of the build
export const dynamic = 'force-dynamic'

export default function RootLayout({ children }) {
throw new Error('Root Layout Error')

return (
<html>
<body>{children}</body>
</html>
)
}
3 changes: 3 additions & 0 deletions test/e2e/app-dir/global-error/with-style-import/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Home() {
return <p>Home</p>
}
30 changes: 30 additions & 0 deletions test/e2e/app-dir/global-error/with-style-import/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { assertHasRedbox, getRedboxHeader } from 'next-test-utils'
import { nextTestSetup } from 'e2e-utils'

async function testDev(browser, errorRegex) {
await assertHasRedbox(browser)
expect(await getRedboxHeader(browser)).toMatch(errorRegex)
}

describe('app dir - global error - with style import', () => {
const { next, isNextDev, skipped } = nextTestSetup({
files: __dirname,
skipDeployment: true,
})

if (skipped) {
return
}

it('should render global error with correct styles', async () => {
const browser = await next.browser('/')

if (isNextDev) {
await testDev(browser, /Root Layout Error/)
return
}

const h2 = await browser.elementByCss('h2')
expect(await h2.getComputedCss('color')).toBe('rgb(255, 255, 0)') // yellow
})
})

0 comments on commit 3356b1c

Please sign in to comment.