Skip to content

Commit db72cb1

Browse files
authored
refactor: Use swc AST to determine use client and server directives (#54358)
Follow up for #54202, use swc AST to determine the directives. Also add server actions directives for extendibility here in case we might need it in the future Closes NEXT-1538
1 parent ad556ae commit db72cb1

File tree

7 files changed

+42
-9
lines changed

7 files changed

+42
-9
lines changed

packages/next/src/build/analysis/get-page-static-info.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,17 @@ export interface PageStaticInfo {
5151

5252
const CLIENT_MODULE_LABEL =
5353
/\/\* __next_internal_client_entry_do_not_use__ ([^ ]*) (cjs|auto) \*\//
54+
5455
const ACTION_MODULE_LABEL =
5556
/\/\* __next_internal_action_entry_do_not_use__ ([^ ]+) \*\//
5657

58+
const CLIENT_DIRECTIVE = 'use client'
59+
const SERVER_ACTION_DIRECTIVE = 'use server'
60+
5761
export type RSCModuleType = 'server' | 'client'
5862
export function getRSCModuleInformation(
5963
source: string,
60-
isServerLayer = true
64+
isServerLayer: boolean
6165
): RSCMeta {
6266
const actions = source.match(ACTION_MODULE_LABEL)?.[1]?.split(',')
6367
const clientInfoMatch = source.match(CLIENT_MODULE_LABEL)
@@ -75,14 +79,13 @@ export function getRSCModuleInformation(
7579
const clientEntryType = clientInfoMatch?.[2] as 'cjs' | 'auto'
7680

7781
const type = clientRefs ? RSC_MODULE_TYPES.client : RSC_MODULE_TYPES.server
78-
const hasUseClientDirective = /^\s*['"]use client['"]/.test(source)
82+
7983
return {
8084
type,
8185
actions,
8286
clientRefs,
8387
clientEntryType,
8488
isClientRef,
85-
hasUseClientDirective,
8689
}
8790
}
8891

@@ -124,6 +127,7 @@ function checkExports(
124127
generateSitemaps?: boolean
125128
generateStaticParams: boolean
126129
extraProperties?: Set<string>
130+
directives?: Set<string>
127131
} {
128132
const exportsSet = new Set<string>([
129133
'getStaticProps',
@@ -142,8 +146,27 @@ function checkExports(
142146
let generateSitemaps: boolean = false
143147
let generateStaticParams = false
144148
let extraProperties = new Set<string>()
149+
let directives = new Set<string>()
150+
let hasLeadingNonDirectiveNode = false
145151

146152
for (const node of swcAST.body) {
153+
// There should be no non-string literals nodes before directives
154+
if (
155+
node.type === 'ExpressionStatement' &&
156+
node.expression.type === 'StringLiteral'
157+
) {
158+
if (!hasLeadingNonDirectiveNode) {
159+
const directive = node.expression.value
160+
if (CLIENT_DIRECTIVE === directive) {
161+
directives.add('client')
162+
}
163+
if (SERVER_ACTION_DIRECTIVE === directive) {
164+
directives.add('server')
165+
}
166+
}
167+
} else {
168+
hasLeadingNonDirectiveNode = true
169+
}
147170
if (
148171
node.type === 'ExportDeclaration' &&
149172
node.declaration?.type === 'VariableDeclaration'
@@ -240,6 +263,7 @@ function checkExports(
240263
generateSitemaps,
241264
generateStaticParams,
242265
extraProperties,
266+
directives,
243267
}
244268
} catch (err) {}
245269
}
@@ -253,6 +277,7 @@ function checkExports(
253277
generateSitemaps: false,
254278
generateStaticParams: false,
255279
extraProperties: undefined,
280+
directives: undefined,
256281
}
257282
}
258283

@@ -467,8 +492,9 @@ export async function getPageStaticInfo(params: {
467492
preferredRegion,
468493
generateStaticParams,
469494
extraProperties,
495+
directives,
470496
} = checkExports(swcAST, pageFilePath)
471-
const rscInfo = getRSCModuleInformation(fileContent)
497+
const rscInfo = getRSCModuleInformation(fileContent, true)
472498
const rsc = rscInfo.type
473499

474500
// default / failsafe value for config
@@ -575,7 +601,7 @@ export async function getPageStaticInfo(params: {
575601

576602
if (
577603
pageType === 'app' &&
578-
rscInfo.hasUseClientDirective &&
604+
directives?.has('client') &&
579605
generateStaticParams
580606
) {
581607
throw new Error(

packages/next/src/build/webpack/loaders/get-module-build-info.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ export interface RSCMeta {
3232
clientRefs?: string[]
3333
clientEntryType?: 'cjs' | 'auto'
3434
isClientRef?: boolean
35-
hasUseClientDirective?: boolean
3635
requests?: string[] // client requests in flight client entry
3736
}
3837

packages/next/src/build/webpack/loaders/next-flight-loader/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export default function transformSource(
2222
// Assign the RSC meta information to buildInfo.
2323
// Exclude next internal files which are not marked as client files
2424
const buildInfo = getModuleBuildInfo(this._module)
25-
buildInfo.rsc = getRSCModuleInformation(source)
25+
buildInfo.rsc = getRSCModuleInformation(source, true)
2626

2727
// A client boundary.
2828
if (buildInfo.rsc?.type === RSC_MODULE_TYPES.client) {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export default function Layout({ children }) {
2+
return children
3+
}
4+
5+
// make children routes dynamic
6+
export const dynamic = 'force-dynamic'

test/e2e/app-dir/rsc-basic/app/css-in-js/suspense/page.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,5 @@ export default function page() {
4141
</div>
4242
)
4343
}
44+
45+
export const dynamic = 'force-dynamic'

test/e2e/app-dir/rsc-basic/app/layout.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import React from 'react'
22
import RootStyleRegistry from './root-style-registry'
33

4-
export const revalidate = 0
5-
64
export default function AppLayout({ children }) {
75
return (
86
<html>

test/e2e/app-dir/rsc-basic/app/partial-hydration/page.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ export default function () {
2323
</>
2424
)
2525
}
26+
27+
export const dynamic = 'force-dynamic'

0 commit comments

Comments
 (0)