From 734e7606b120a0bf00d33835dd63f9e1e93a9e69 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 22 Aug 2023 00:39:08 +0200 Subject: [PATCH 1/7] Use swc AST to determine use client and server directives --- .../build/analysis/get-page-static-info.ts | 25 ++++++++++++++++--- .../webpack/loaders/get-module-build-info.ts | 1 - 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/next/src/build/analysis/get-page-static-info.ts b/packages/next/src/build/analysis/get-page-static-info.ts index 63699cd8f8b4b..0f13c96a30b12 100644 --- a/packages/next/src/build/analysis/get-page-static-info.ts +++ b/packages/next/src/build/analysis/get-page-static-info.ts @@ -51,9 +51,13 @@ export interface PageStaticInfo { const CLIENT_MODULE_LABEL = /\/\* __next_internal_client_entry_do_not_use__ ([^ ]*) (cjs|auto) \*\// + const ACTION_MODULE_LABEL = /\/\* __next_internal_action_entry_do_not_use__ ([^ ]+) \*\// +const CLIENT_DIRECTIVE_REGEX = /^use client$/ +const SERVER_ACTION_DIRECTIVE_REGEX = /^use server$/ + export type RSCModuleType = 'server' | 'client' export function getRSCModuleInformation( source: string, @@ -75,14 +79,12 @@ export function getRSCModuleInformation( const clientEntryType = clientInfoMatch?.[2] as 'cjs' | 'auto' const type = clientRefs ? RSC_MODULE_TYPES.client : RSC_MODULE_TYPES.server - const hasUseClientDirective = /^\s*['"]use client['"]/.test(source) return { type, actions, clientRefs, clientEntryType, isClientRef, - hasUseClientDirective, } } @@ -124,6 +126,7 @@ function checkExports( generateSitemaps?: boolean generateStaticParams: boolean extraProperties?: Set + directives?: Set } { const exportsSet = new Set([ 'getStaticProps', @@ -142,6 +145,7 @@ function checkExports( let generateSitemaps: boolean = false let generateStaticParams = false let extraProperties = new Set() + let directives = new Set() for (const node of swcAST.body) { if ( @@ -229,6 +233,18 @@ function checkExports( ) } } + + if (node.type === 'ExpressionStatement') { + if (node.expression.type === 'StringLiteral') { + const directive = node.expression.value + if (CLIENT_DIRECTIVE_REGEX.test(directive)) { + directives.add('client') + } + if (SERVER_ACTION_DIRECTIVE_REGEX.test(directive)) { + directives.add('server') + } + } + } } return { @@ -240,6 +256,7 @@ function checkExports( generateSitemaps, generateStaticParams, extraProperties, + directives, } } catch (err) {} } @@ -253,6 +270,7 @@ function checkExports( generateSitemaps: false, generateStaticParams: false, extraProperties: undefined, + directives: undefined, } } @@ -467,6 +485,7 @@ export async function getPageStaticInfo(params: { preferredRegion, generateStaticParams, extraProperties, + directives, } = checkExports(swcAST, pageFilePath) const rscInfo = getRSCModuleInformation(fileContent) const rsc = rscInfo.type @@ -575,7 +594,7 @@ export async function getPageStaticInfo(params: { if ( pageType === 'app' && - rscInfo.hasUseClientDirective && + directives?.has('client') && generateStaticParams ) { throw new Error( diff --git a/packages/next/src/build/webpack/loaders/get-module-build-info.ts b/packages/next/src/build/webpack/loaders/get-module-build-info.ts index e67d061db736f..f0ca52bf2f864 100644 --- a/packages/next/src/build/webpack/loaders/get-module-build-info.ts +++ b/packages/next/src/build/webpack/loaders/get-module-build-info.ts @@ -32,7 +32,6 @@ export interface RSCMeta { clientRefs?: string[] clientEntryType?: 'cjs' | 'auto' isClientRef?: boolean - hasUseClientDirective?: boolean requests?: string[] // client requests in flight client entry } From 873cd2fb6b4615d7bb3c217fdeb17e3ab1fadd71 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 22 Aug 2023 01:01:29 +0200 Subject: [PATCH 2/7] use string comp --- packages/next/src/build/analysis/get-page-static-info.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/next/src/build/analysis/get-page-static-info.ts b/packages/next/src/build/analysis/get-page-static-info.ts index 0f13c96a30b12..1fa48058c896a 100644 --- a/packages/next/src/build/analysis/get-page-static-info.ts +++ b/packages/next/src/build/analysis/get-page-static-info.ts @@ -55,8 +55,8 @@ const CLIENT_MODULE_LABEL = const ACTION_MODULE_LABEL = /\/\* __next_internal_action_entry_do_not_use__ ([^ ]+) \*\// -const CLIENT_DIRECTIVE_REGEX = /^use client$/ -const SERVER_ACTION_DIRECTIVE_REGEX = /^use server$/ +const CLIENT_DIRECTIVE = 'use client' +const SERVER_ACTION_DIRECTIVE = 'use server' export type RSCModuleType = 'server' | 'client' export function getRSCModuleInformation( @@ -237,10 +237,10 @@ function checkExports( if (node.type === 'ExpressionStatement') { if (node.expression.type === 'StringLiteral') { const directive = node.expression.value - if (CLIENT_DIRECTIVE_REGEX.test(directive)) { + if (CLIENT_DIRECTIVE === directive) { directives.add('client') } - if (SERVER_ACTION_DIRECTIVE_REGEX.test(directive)) { + if (SERVER_ACTION_DIRECTIVE === directive) { directives.add('server') } } From 25405c3cce607a614d5d221aafce6dcddd0fdc14 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 22 Aug 2023 01:29:58 +0200 Subject: [PATCH 3/7] check if directives are top level --- .../build/analysis/get-page-static-info.ts | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/packages/next/src/build/analysis/get-page-static-info.ts b/packages/next/src/build/analysis/get-page-static-info.ts index 1fa48058c896a..0cd83c33ef8a5 100644 --- a/packages/next/src/build/analysis/get-page-static-info.ts +++ b/packages/next/src/build/analysis/get-page-static-info.ts @@ -146,8 +146,26 @@ function checkExports( let generateStaticParams = false let extraProperties = new Set() let directives = new Set() + let hasNonExpressionStatementNode = false for (const node of swcAST.body) { + // There should be no non-string literals nodes before directives + if ( + node.type === 'ExpressionStatement' && + node.expression.type === 'StringLiteral' + ) { + if (!hasNonExpressionStatementNode) { + const directive = node.expression.value + if (CLIENT_DIRECTIVE === directive) { + directives.add('client') + } + if (SERVER_ACTION_DIRECTIVE === directive) { + directives.add('server') + } + } + } else { + hasNonExpressionStatementNode = true + } if ( node.type === 'ExportDeclaration' && node.declaration?.type === 'VariableDeclaration' @@ -233,18 +251,6 @@ function checkExports( ) } } - - if (node.type === 'ExpressionStatement') { - if (node.expression.type === 'StringLiteral') { - const directive = node.expression.value - if (CLIENT_DIRECTIVE === directive) { - directives.add('client') - } - if (SERVER_ACTION_DIRECTIVE === directive) { - directives.add('server') - } - } - } } return { From 0b4830411cb04ab6d9bbbdc83b905c6a00600b04 Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 22 Aug 2023 17:50:10 +0200 Subject: [PATCH 4/7] Fix swc compiling --- .../next-swc/crates/core/src/react_server_components.rs | 4 ++++ .../server-graph/fake-client-entry/input.js | 5 +++++ .../server-graph/fake-client-entry/output.js | 3 +++ .../server-graph/fake-client-entry/output.stderr | 7 +++++++ packages/next/src/build/analysis/get-page-static-info.ts | 9 +++++---- .../build/webpack/loaders/next-flight-loader/index.ts | 2 +- test/e2e/app-dir/rsc-basic/app/layout.js | 2 -- 7 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/fake-client-entry/input.js create mode 100644 packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/fake-client-entry/output.js create mode 100644 packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/fake-client-entry/output.stderr 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 1c483a1ab3311..1435e38eafa3c 100644 --- a/packages/next-swc/crates/core/src/react_server_components.rs +++ b/packages/next-swc/crates/core/src/react_server_components.rs @@ -222,6 +222,7 @@ impl ReactServerComponents { }, }) } + finished_directives = true; } ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { decl, .. })) => { match decl { @@ -240,18 +241,21 @@ impl ReactServerComponents { } _ => {} } + finished_directives = true; } ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl { decl: _, .. })) => { self.export_names.push("default".to_string()); + finished_directives = true; } ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(ExportDefaultExpr { expr: _, .. })) => { self.export_names.push("default".to_string()); + finished_directives = true; } ModuleItem::ModuleDecl(ModuleDecl::ExportAll(_)) => { self.export_names.push("*".to_string()); diff --git a/packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/fake-client-entry/input.js b/packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/fake-client-entry/input.js new file mode 100644 index 0000000000000..decc3848b224d --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/fake-client-entry/input.js @@ -0,0 +1,5 @@ +export default function () { + return null +} + +'use client' \ No newline at end of file diff --git a/packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/fake-client-entry/output.js b/packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/fake-client-entry/output.js new file mode 100644 index 0000000000000..33997bb6e2495 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/fake-client-entry/output.js @@ -0,0 +1,3 @@ +export default function() { + return null; +} diff --git a/packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/fake-client-entry/output.stderr b/packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/fake-client-entry/output.stderr new file mode 100644 index 0000000000000..8da2c69a3b137 --- /dev/null +++ b/packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/fake-client-entry/output.stderr @@ -0,0 +1,7 @@ + + x NEXT_RSC_ERR_CLIENT_DIRECTIVE + ,-[input.js:4:1] + 4 | + 5 | 'use client' + : ^^^^^^^^^^^^ + `---- diff --git a/packages/next/src/build/analysis/get-page-static-info.ts b/packages/next/src/build/analysis/get-page-static-info.ts index 0cd83c33ef8a5..857364e971031 100644 --- a/packages/next/src/build/analysis/get-page-static-info.ts +++ b/packages/next/src/build/analysis/get-page-static-info.ts @@ -50,10 +50,10 @@ export interface PageStaticInfo { } const CLIENT_MODULE_LABEL = - /\/\* __next_internal_client_entry_do_not_use__ ([^ ]*) (cjs|auto) \*\// + /^\/\* __next_internal_client_entry_do_not_use__ ([^ ]*) (cjs|auto) \*\// const ACTION_MODULE_LABEL = - /\/\* __next_internal_action_entry_do_not_use__ ([^ ]+) \*\// + /^\/\* __next_internal_action_entry_do_not_use__ ([^ ]+) \*\// const CLIENT_DIRECTIVE = 'use client' const SERVER_ACTION_DIRECTIVE = 'use server' @@ -61,7 +61,7 @@ const SERVER_ACTION_DIRECTIVE = 'use server' export type RSCModuleType = 'server' | 'client' export function getRSCModuleInformation( source: string, - isServerLayer = true + isServerLayer: boolean ): RSCMeta { const actions = source.match(ACTION_MODULE_LABEL)?.[1]?.split(',') const clientInfoMatch = source.match(CLIENT_MODULE_LABEL) @@ -79,6 +79,7 @@ export function getRSCModuleInformation( const clientEntryType = clientInfoMatch?.[2] as 'cjs' | 'auto' const type = clientRefs ? RSC_MODULE_TYPES.client : RSC_MODULE_TYPES.server + return { type, actions, @@ -493,7 +494,7 @@ export async function getPageStaticInfo(params: { extraProperties, directives, } = checkExports(swcAST, pageFilePath) - const rscInfo = getRSCModuleInformation(fileContent) + const rscInfo = getRSCModuleInformation(fileContent, true) const rsc = rscInfo.type // default / failsafe value for config diff --git a/packages/next/src/build/webpack/loaders/next-flight-loader/index.ts b/packages/next/src/build/webpack/loaders/next-flight-loader/index.ts index 315b49d18cc78..7b9e43fc35069 100644 --- a/packages/next/src/build/webpack/loaders/next-flight-loader/index.ts +++ b/packages/next/src/build/webpack/loaders/next-flight-loader/index.ts @@ -22,7 +22,7 @@ export default function transformSource( // Assign the RSC meta information to buildInfo. // Exclude next internal files which are not marked as client files const buildInfo = getModuleBuildInfo(this._module) - buildInfo.rsc = getRSCModuleInformation(source) + buildInfo.rsc = getRSCModuleInformation(source, true) // A client boundary. if (buildInfo.rsc?.type === RSC_MODULE_TYPES.client) { diff --git a/test/e2e/app-dir/rsc-basic/app/layout.js b/test/e2e/app-dir/rsc-basic/app/layout.js index e9a9ebba67353..4a732e89c2c8f 100644 --- a/test/e2e/app-dir/rsc-basic/app/layout.js +++ b/test/e2e/app-dir/rsc-basic/app/layout.js @@ -1,8 +1,6 @@ import React from 'react' import RootStyleRegistry from './root-style-registry' -export const revalidate = 0 - export default function AppLayout({ children }) { return ( From e44a73e077d3eed49ce7f658e949b25e256c15bf Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 22 Aug 2023 18:25:54 +0200 Subject: [PATCH 5/7] fix test --- .../server-graph/fake-client-entry/input.js | 2 +- test/e2e/app-dir/rsc-basic/app/css-in-js/suspense/page.js | 2 ++ test/e2e/app-dir/rsc-basic/app/partial-hydration/page.js | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/fake-client-entry/input.js b/packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/fake-client-entry/input.js index decc3848b224d..cb1e0dcca1450 100644 --- a/packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/fake-client-entry/input.js +++ b/packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/fake-client-entry/input.js @@ -2,4 +2,4 @@ export default function () { return null } -'use client' \ No newline at end of file +;('use client') diff --git a/test/e2e/app-dir/rsc-basic/app/css-in-js/suspense/page.js b/test/e2e/app-dir/rsc-basic/app/css-in-js/suspense/page.js index 21d9ff23d9e07..5664c84fa9960 100644 --- a/test/e2e/app-dir/rsc-basic/app/css-in-js/suspense/page.js +++ b/test/e2e/app-dir/rsc-basic/app/css-in-js/suspense/page.js @@ -41,3 +41,5 @@ export default function page() { ) } + +export const dynamic = 'force-dynamic' diff --git a/test/e2e/app-dir/rsc-basic/app/partial-hydration/page.js b/test/e2e/app-dir/rsc-basic/app/partial-hydration/page.js index ef8b0999fe821..c9cb78051ad57 100644 --- a/test/e2e/app-dir/rsc-basic/app/partial-hydration/page.js +++ b/test/e2e/app-dir/rsc-basic/app/partial-hydration/page.js @@ -23,3 +23,5 @@ export default function () { ) } + +export const dynamic = 'force-dynamic' From fe3d6864f4431389f8663b8a4d99e0c9f687193b Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 22 Aug 2023 22:36:46 +0200 Subject: [PATCH 6/7] revert --- packages/next/src/build/analysis/get-page-static-info.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/next/src/build/analysis/get-page-static-info.ts b/packages/next/src/build/analysis/get-page-static-info.ts index 857364e971031..e45a4f547086f 100644 --- a/packages/next/src/build/analysis/get-page-static-info.ts +++ b/packages/next/src/build/analysis/get-page-static-info.ts @@ -50,10 +50,10 @@ export interface PageStaticInfo { } const CLIENT_MODULE_LABEL = - /^\/\* __next_internal_client_entry_do_not_use__ ([^ ]*) (cjs|auto) \*\// + /\/\* __next_internal_client_entry_do_not_use__ ([^ ]*) (cjs|auto) \*\// const ACTION_MODULE_LABEL = - /^\/\* __next_internal_action_entry_do_not_use__ ([^ ]+) \*\// + /\/\* __next_internal_action_entry_do_not_use__ ([^ ]+) \*\// const CLIENT_DIRECTIVE = 'use client' const SERVER_ACTION_DIRECTIVE = 'use server' From 6f6f582e1f1a495346c1a382428c483ff99c4e8d Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Tue, 22 Aug 2023 22:53:21 +0200 Subject: [PATCH 7/7] fix test case --- packages/next/src/build/analysis/get-page-static-info.ts | 6 +++--- test/e2e/app-dir/rsc-basic/app/css-in-js/layout.js | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 test/e2e/app-dir/rsc-basic/app/css-in-js/layout.js diff --git a/packages/next/src/build/analysis/get-page-static-info.ts b/packages/next/src/build/analysis/get-page-static-info.ts index e45a4f547086f..9951b583b1f04 100644 --- a/packages/next/src/build/analysis/get-page-static-info.ts +++ b/packages/next/src/build/analysis/get-page-static-info.ts @@ -147,7 +147,7 @@ function checkExports( let generateStaticParams = false let extraProperties = new Set() let directives = new Set() - let hasNonExpressionStatementNode = false + let hasLeadingNonDirectiveNode = false for (const node of swcAST.body) { // There should be no non-string literals nodes before directives @@ -155,7 +155,7 @@ function checkExports( node.type === 'ExpressionStatement' && node.expression.type === 'StringLiteral' ) { - if (!hasNonExpressionStatementNode) { + if (!hasLeadingNonDirectiveNode) { const directive = node.expression.value if (CLIENT_DIRECTIVE === directive) { directives.add('client') @@ -165,7 +165,7 @@ function checkExports( } } } else { - hasNonExpressionStatementNode = true + hasLeadingNonDirectiveNode = true } if ( node.type === 'ExportDeclaration' && diff --git a/test/e2e/app-dir/rsc-basic/app/css-in-js/layout.js b/test/e2e/app-dir/rsc-basic/app/css-in-js/layout.js new file mode 100644 index 0000000000000..9ff469953f6e3 --- /dev/null +++ b/test/e2e/app-dir/rsc-basic/app/css-in-js/layout.js @@ -0,0 +1,6 @@ +export default function Layout({ children }) { + return children +} + +// make children routes dynamic +export const dynamic = 'force-dynamic'