From 3356b1c28bfd81fc0178ebc2eff5379f8f459fa2 Mon Sep 17 00:00:00 2001 From: Jude Gao Date: Sat, 26 Oct 2024 23:21:07 -0400 Subject: [PATCH] fix global-error styles --- crates/next-core/src/app_page_loader_tree.rs | 4 +- crates/next-core/src/base_loader_tree.rs | 2 + .../webpack/loaders/next-app-loader/index.ts | 1 + packages/next/src/client/app-index.tsx | 2 +- .../next/src/client/components/app-router.tsx | 9 +++-- .../next/src/server/app-render/app-render.tsx | 37 +++++++++++++++++-- packages/next/src/server/app-render/types.ts | 2 +- .../with-style-import/app/global-error.tsx | 13 +++++++ .../with-style-import/app/global.css | 3 ++ .../with-style-import/app/layout.tsx | 12 ++++++ .../with-style-import/app/page.tsx | 3 ++ .../with-style-import/index.test.ts | 30 +++++++++++++++ 12 files changed, 108 insertions(+), 10 deletions(-) create mode 100644 test/e2e/app-dir/global-error/with-style-import/app/global-error.tsx create mode 100644 test/e2e/app-dir/global-error/with-style-import/app/global.css create mode 100644 test/e2e/app-dir/global-error/with-style-import/app/layout.tsx create mode 100644 test/e2e/app-dir/global-error/with-style-import/app/page.tsx create mode 100644 test/e2e/app-dir/global-error/with-style-import/index.test.ts diff --git a/crates/next-core/src/app_page_loader_tree.rs b/crates/next-core/src/app_page_loader_tree.rs index c13636040b6e5..379b1934b9eff 100644 --- a/crates/next-core/src/app_page_loader_tree.rs +++ b/crates/next-core/src/app_page_loader_tree.rs @@ -311,7 +311,7 @@ impl AppPageLoaderTreeBuilder { page, default, error, - global_error: _, + global_error, layout, loading, template, @@ -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); diff --git a/crates/next-core/src/base_loader_tree.rs b/crates/next-core/src/base_loader_tree.rs index 64b7991b50cbf..58d1a715b4c8a 100644 --- a/crates/next-core/src/base_loader_tree.rs +++ b/crates/next-core/src/base_loader_tree.rs @@ -28,6 +28,7 @@ pub enum AppDirModuleType { Loading, Template, NotFound, + GlobalError, } impl AppDirModuleType { @@ -40,6 +41,7 @@ impl AppDirModuleType { AppDirModuleType::Loading => "loading", AppDirModuleType::Template => "template", AppDirModuleType::NotFound => "not-found", + AppDirModuleType::GlobalError => "global-error", } } } diff --git a/packages/next/src/build/webpack/loaders/next-app-loader/index.ts b/packages/next/src/build/webpack/loaders/next-app-loader/index.ts index fc0f051e848cd..7366fac676841 100644 --- a/packages/next/src/build/webpack/loaders/next-app-loader/index.ts +++ b/packages/next/src/build/webpack/loaders/next-app-loader/index.ts @@ -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' diff --git a/packages/next/src/client/app-index.tsx b/packages/next/src/client/app-index.tsx index c39d36c01316a..d191a8b8d8593 100644 --- a/packages/next/src/client/app-index.tsx +++ b/packages/next/src/client/app-index.tsx @@ -185,7 +185,7 @@ function ServerRoot(): React.ReactNode { const router = ( ) diff --git a/packages/next/src/client/components/app-router.tsx b/packages/next/src/client/components/app-router.tsx index 5e65ac0710af9..bb46114e9ce3b 100644 --- a/packages/next/src/client/components/app-router.tsx +++ b/packages/next/src/client/components/app-router.tsx @@ -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 ( - + ) diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 1dd03b858cfbf..c35e1c10d2475 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -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] @@ -724,6 +726,8 @@ async function getRSCPayload( ) + const globalErrorStyles = await getGlobalErrorStyles(tree, ctx) + return { // See the comment above the `Preloads` component (below) for why this is part of the payload P: , @@ -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, } @@ -818,6 +822,8 @@ async function getErrorRSCPayload( null, ] + const globalErrorStyles = await getGlobalErrorStyles(tree, ctx) + return { b: ctx.renderOpts.buildId, p: ctx.assetPrefix, @@ -825,7 +831,7 @@ async function getErrorRSCPayload( m: undefined, i: false, f: [[initialTree, initialSeedData, initialHead]], - G: GlobalError, + G: [GlobalError, globalErrorStyles], s: typeof ctx.renderOpts.postponed === 'string', S: workStore.isStaticGeneration, } satisfies InitialRSCPayload @@ -882,7 +888,7 @@ function App({ @@ -931,7 +937,7 @@ function AppWithoutContext({ return ( ) @@ -3814,3 +3820,26 @@ export async function warmFlightResponse( chunkListeners.push(r) }) } + +const getGlobalErrorStyles = async ( + tree: LoaderTree, + ctx: AppRenderContext +): Promise => { + 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 +} diff --git a/packages/next/src/server/app-render/types.ts b/packages/next/src/server/app-render/types.ts index 54b71d0890687..d4e486c4dae42 100644 --- a/packages/next/src/server/app-render/types.ts +++ b/packages/next/src/server/app-render/types.ts @@ -226,7 +226,7 @@ export type InitialRSCPayload = { /** missingSlots */ m: Set | undefined /** GlobalError */ - G: React.ComponentType + G: [React.ComponentType, React.ReactNode | undefined] /** postponed */ s: boolean /** prerendered */ diff --git a/test/e2e/app-dir/global-error/with-style-import/app/global-error.tsx b/test/e2e/app-dir/global-error/with-style-import/app/global-error.tsx new file mode 100644 index 0000000000000..da7abe567878c --- /dev/null +++ b/test/e2e/app-dir/global-error/with-style-import/app/global-error.tsx @@ -0,0 +1,13 @@ +'use client' + +import './global.css' + +export default function GlobalError() { + return ( + + +

Error!

+ + + ) +} diff --git a/test/e2e/app-dir/global-error/with-style-import/app/global.css b/test/e2e/app-dir/global-error/with-style-import/app/global.css new file mode 100644 index 0000000000000..87ecf4409f47c --- /dev/null +++ b/test/e2e/app-dir/global-error/with-style-import/app/global.css @@ -0,0 +1,3 @@ +h2 { + color: yellow; +} diff --git a/test/e2e/app-dir/global-error/with-style-import/app/layout.tsx b/test/e2e/app-dir/global-error/with-style-import/app/layout.tsx new file mode 100644 index 0000000000000..09b3da3a13ea7 --- /dev/null +++ b/test/e2e/app-dir/global-error/with-style-import/app/layout.tsx @@ -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 ( + + {children} + + ) +} diff --git a/test/e2e/app-dir/global-error/with-style-import/app/page.tsx b/test/e2e/app-dir/global-error/with-style-import/app/page.tsx new file mode 100644 index 0000000000000..c303be8817213 --- /dev/null +++ b/test/e2e/app-dir/global-error/with-style-import/app/page.tsx @@ -0,0 +1,3 @@ +export default function Home() { + return

Home

+} diff --git a/test/e2e/app-dir/global-error/with-style-import/index.test.ts b/test/e2e/app-dir/global-error/with-style-import/index.test.ts new file mode 100644 index 0000000000000..22ef8abf6dea6 --- /dev/null +++ b/test/e2e/app-dir/global-error/with-style-import/index.test.ts @@ -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 + }) +})