From 1aebc1cca437c90de89211bf8055264bc3c95bff Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Fri, 6 Oct 2023 01:11:46 +0200 Subject: [PATCH 1/2] Loose rsc api assertion errors for 3rd party packages --- .../core/src/react_server_components.rs | 19 +++++ .../fixtures/rsc-build-errors/next.config.js | 1 - .../rsc-runtime-errors/app/client/page.js | 4 + .../fixtures/rsc-runtime-errors/app/layout.js | 12 +++ .../rsc-runtime-errors/app/server/page.js | 3 + .../node_modules_bak/client-package/index.js | 5 ++ .../client-package/package.json | 4 + .../node_modules_bak/server-package/index.js | 7 ++ .../server-package/package.json | 4 + .../acceptance-app/rsc-build-errors.test.ts | 59 -------------- .../acceptance-app/rsc-runtime-errors.test.ts | 80 +++++++++++++++++++ 11 files changed, 138 insertions(+), 60 deletions(-) delete mode 100644 test/development/acceptance-app/fixtures/rsc-build-errors/next.config.js create mode 100644 test/development/acceptance-app/fixtures/rsc-runtime-errors/app/client/page.js create mode 100644 test/development/acceptance-app/fixtures/rsc-runtime-errors/app/layout.js create mode 100644 test/development/acceptance-app/fixtures/rsc-runtime-errors/app/server/page.js create mode 100644 test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/client-package/index.js create mode 100644 test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/client-package/package.json create mode 100644 test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/server-package/index.js create mode 100644 test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/server-package/package.json create mode 100644 test/development/acceptance-app/rsc-runtime-errors.test.ts diff --git a/packages/next-swc/crates/core/src/react_server_components.rs b/packages/next-swc/crates/core/src/react_server_components.rs index 8dd923dc61c74..8d136e78b09d8 100644 --- a/packages/next-swc/crates/core/src/react_server_components.rs +++ b/packages/next-swc/crates/core/src/react_server_components.rs @@ -344,6 +344,10 @@ impl ReactServerComponents { } fn assert_server_graph(&self, imports: &[ModuleImports], module: &Module) { + // If the + if self.is_from_node_modules(&self.filepath) { + return; + } for import in imports { let source = import.source.0.clone(); if self.invalid_server_imports.contains(&source) { @@ -391,6 +395,9 @@ impl ReactServerComponents { } fn assert_server_filename(&self, module: &Module) { + if self.is_from_node_modules(&self.filepath) { + return; + } let is_error_file = Regex::new(r"[\\/]error\.(ts|js)x?$") .unwrap() .is_match(&self.filepath); @@ -416,6 +423,9 @@ impl ReactServerComponents { } fn assert_client_graph(&self, imports: &[ModuleImports]) { + if self.is_from_node_modules(&self.filepath) { + return; + } for import in imports { let source = import.source.0.clone(); if self.invalid_client_imports.contains(&source) { @@ -432,6 +442,9 @@ impl ReactServerComponents { } fn assert_invalid_api(&self, module: &Module, is_client_entry: bool) { + if self.is_from_node_modules(&self.filepath) { + return; + } let is_layout_or_page = Regex::new(r"[\\/](page|layout)\.(ts|js)x?$") .unwrap() .is_match(&self.filepath); @@ -562,6 +575,12 @@ impl ReactServerComponents { }, ); } + + fn is_from_node_modules(&self, filepath: &str) -> bool { + Regex::new(r"[\\/]node_modules[\\/]") + .unwrap() + .is_match(filepath) + } } pub fn server_components( diff --git a/test/development/acceptance-app/fixtures/rsc-build-errors/next.config.js b/test/development/acceptance-app/fixtures/rsc-build-errors/next.config.js deleted file mode 100644 index 4ba52ba2c8df6..0000000000000 --- a/test/development/acceptance-app/fixtures/rsc-build-errors/next.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {} diff --git a/test/development/acceptance-app/fixtures/rsc-runtime-errors/app/client/page.js b/test/development/acceptance-app/fixtures/rsc-runtime-errors/app/client/page.js new file mode 100644 index 0000000000000..33f94c1f46b07 --- /dev/null +++ b/test/development/acceptance-app/fixtures/rsc-runtime-errors/app/client/page.js @@ -0,0 +1,4 @@ +'use client' +export default function page() { + return 'page' +} diff --git a/test/development/acceptance-app/fixtures/rsc-runtime-errors/app/layout.js b/test/development/acceptance-app/fixtures/rsc-runtime-errors/app/layout.js new file mode 100644 index 0000000000000..8525f5f8c0b2a --- /dev/null +++ b/test/development/acceptance-app/fixtures/rsc-runtime-errors/app/layout.js @@ -0,0 +1,12 @@ +export const metadata = { + title: 'Next.js', + description: 'Generated by Next.js', +} + +export default function RootLayout({ children }) { + return ( + + {children} + + ) +} diff --git a/test/development/acceptance-app/fixtures/rsc-runtime-errors/app/server/page.js b/test/development/acceptance-app/fixtures/rsc-runtime-errors/app/server/page.js new file mode 100644 index 0000000000000..41c1c6b78dffe --- /dev/null +++ b/test/development/acceptance-app/fixtures/rsc-runtime-errors/app/server/page.js @@ -0,0 +1,3 @@ +export default function page() { + return 'page' +} diff --git a/test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/client-package/index.js b/test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/client-package/index.js new file mode 100644 index 0000000000000..e7552d90e4c6d --- /dev/null +++ b/test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/client-package/index.js @@ -0,0 +1,5 @@ +import { useState } from 'react' + +export function callClientApi() { + return useState(0) +} diff --git a/test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/client-package/package.json b/test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/client-package/package.json new file mode 100644 index 0000000000000..5f196b3ba2bd8 --- /dev/null +++ b/test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/client-package/package.json @@ -0,0 +1,4 @@ +{ + "name": "client-package", + "exports": "./index.js" +} diff --git a/test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/server-package/index.js b/test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/server-package/index.js new file mode 100644 index 0000000000000..a0b959e43331a --- /dev/null +++ b/test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/server-package/index.js @@ -0,0 +1,7 @@ +'use client' + +import { cookies } from 'next/headers' + +export function callServerApi() { + return cookies() +} diff --git a/test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/server-package/package.json b/test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/server-package/package.json new file mode 100644 index 0000000000000..ba9bc67edc39b --- /dev/null +++ b/test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/server-package/package.json @@ -0,0 +1,4 @@ +{ + "name": "server-package", + "exports": "./index.js" +} diff --git a/test/development/acceptance-app/rsc-build-errors.test.ts b/test/development/acceptance-app/rsc-build-errors.test.ts index 1e8bbacb26e4e..f65a33bc38a4c 100644 --- a/test/development/acceptance-app/rsc-build-errors.test.ts +++ b/test/development/acceptance-app/rsc-build-errors.test.ts @@ -379,63 +379,4 @@ describe('Error overlay - RSC build errors', () => { await cleanup() }) - - it('should show which import caused an error in node_modules', async () => { - const { session, cleanup } = await sandbox( - next, - new Map([ - [ - 'node_modules/client-package/module2.js', - "import { useState } from 'react'", - ], - ['node_modules/client-package/module1.js', "import './module2.js'"], - ['node_modules/client-package/index.js', "import './module1.js'"], - [ - 'node_modules/client-package/package.json', - outdent` - { - "name": "client-package", - "version": "0.0.1" - } - `, - ], - ['app/Component.js', "import 'client-package'"], - [ - 'app/page.js', - outdent` - import './Component.js' - export default function Page() { - return

Hello world

- } - `, - ], - ]) - ) - - expect(await session.hasRedbox(true)).toBe(true) - expect( - next.normalizeTestDirContent(await session.getRedboxSource()) - ).toMatchInlineSnapshot( - next.normalizeSnapshot(` - "./app/Component.js - ReactServerComponentsError: - - You're importing a component that needs useState. It only works in a Client Component but none of its parents are marked with \\"use client\\", so they're Server Components by default. - Learn more: https://nextjs.org/docs/getting-started/react-essentials - - ,-[TEST_DIR/node_modules/client-package/module2.js:1:1] - 1 | import { useState } from 'react' - : ^^^^^^^^ - \`---- - - The error was caused by importing 'client-package/index.js' in './app/Component.js'. - - Maybe one of these should be marked as a client entry with \\"use client\\": - ./app/Component.js - ./app/page.js" - `) - ) - - await cleanup() - }) }) diff --git a/test/development/acceptance-app/rsc-runtime-errors.test.ts b/test/development/acceptance-app/rsc-runtime-errors.test.ts new file mode 100644 index 0000000000000..41b2688f31585 --- /dev/null +++ b/test/development/acceptance-app/rsc-runtime-errors.test.ts @@ -0,0 +1,80 @@ +import path from 'path' +import { outdent } from 'outdent' +import { FileRef, createNextDescribe } from 'e2e-utils' +import { + check, + getRedboxDescription, + hasRedbox, + shouldRunTurboDevTest, +} from 'next-test-utils' + +createNextDescribe( + 'Error overlay - RSC runtime errors', + { + files: new FileRef(path.join(__dirname, 'fixtures', 'rsc-runtime-errors')), + packageJson: { + scripts: { + setup: 'cp -r ./node_modules_bak/* ./node_modules', + build: 'yarn setup && next build', + dev: `yarn setup && next ${ + shouldRunTurboDevTest() ? 'dev --turbo' : 'dev' + }`, + start: 'next start', + }, + }, + installCommand: 'yarn', + startCommand: (global as any).isNextDev ? 'yarn dev' : 'yarn start', + }, + ({ next }) => { + it('should show runtime errors if invalid client API from node_modules is executed', async () => { + await next.patchFile( + 'app/server/page.js', + outdent` + import { callClientApi } from 'client-package' + export default function Page() { + callClientApi() + return 'page' + } + ` + ) + + const browser = await next.browser('/server') + + await check( + async () => ((await hasRedbox(browser, true)) ? 'success' : 'fail'), + /success/ + ) + const errorDescription = await getRedboxDescription(browser) + + expect(errorDescription).toContain( + `Error: useState only works in Client Components. Add the "use client" directive at the top of the file to use it. Read more: https://nextjs.org/docs/messages/react-client-hook-in-server-component` + ) + }) + + it('should show runtime errors if invalid server API from node_modules is executed', async () => { + await next.patchFile( + 'app/client/page.js', + outdent` + 'use client' + import { callServerApi } from 'server-package' + export default function Page() { + callServerApi() + return 'page' + } + ` + ) + + const browser = await next.browser('/client') + + await check( + async () => ((await hasRedbox(browser, true)) ? 'success' : 'fail'), + /success/ + ) + const errorDescription = await getRedboxDescription(browser) + + expect(errorDescription).toContain( + `Error: Invariant: cookies() expects to have requestAsyncStorage, none available.` + ) + }) + } +) From 8262d9e0065487f8194a6b0e4595d42c5b441d53 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Fri, 6 Oct 2023 01:30:44 +0200 Subject: [PATCH 2/2] fix lint --- .../rsc-runtime-errors/node_modules_bak/client-package/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/client-package/index.js b/test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/client-package/index.js index e7552d90e4c6d..6e5c0b4e88205 100644 --- a/test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/client-package/index.js +++ b/test/development/acceptance-app/fixtures/rsc-runtime-errors/node_modules_bak/client-package/index.js @@ -1,5 +1,6 @@ import { useState } from 'react' export function callClientApi() { + // eslint-disable-next-line react-hooks/rules-of-hooks return useState(0) }