Skip to content

Commit 53e9524

Browse files
KyleAMathewsclaude
andcommitted
fix(router-plugin): respect directive prologues when inserting imports
Add insertAfterDirectives() helper to ensure imports are inserted after directive prologues ('use client', 'use server', etc.) rather than before them. This is critical for React Server Components and other frameworks that require directives to be the first statements in a module. Previously, unshiftContainer() would insert imports at the start of the program body, potentially before directives, which would cause them to be ignored or throw errors. All 5 usages of unshiftContainer() have been replaced with the new helper: - LazyRouteComponent imports - Dynamic import importer functions - lazyFn imports - Shared variable imports (with correct index tracking) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 7295859 commit 53e9524

File tree

1 file changed

+37
-7
lines changed

1 file changed

+37
-7
lines changed

packages/router-plugin/src/core/code-splitter/compilers.ts

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,34 @@ function isTopLevelVarDecl(
146146
return false
147147
}
148148

149+
/**
150+
* Insert nodes into the program body after any directive prologues ('use client', 'use server', etc.)
151+
* This ensures directives remain first in the file, which is required by frameworks like React Server Components.
152+
* Returns the index where the nodes were inserted.
153+
*/
154+
function insertAfterDirectives(
155+
programPath: babel.NodePath<t.Program>,
156+
nodes: Array<t.Statement> | t.Statement,
157+
): number {
158+
const body = programPath.get('body') as Array<babel.NodePath>
159+
let insertIndex = 0
160+
161+
// Find the first non-directive statement
162+
while (
163+
insertIndex < body.length &&
164+
body[insertIndex]!.isExpressionStatement() &&
165+
t.isDirectiveLiteral((body[insertIndex]!.node as any).expression)
166+
) {
167+
insertIndex++
168+
}
169+
170+
// Insert at the calculated position
171+
const nodesToInsert = Array.isArray(nodes) ? nodes : [nodes]
172+
programPath.node.body.splice(insertIndex, 0, ...nodesToInsert)
173+
174+
return insertIndex
175+
}
176+
149177
export function compileCodeSplitReferenceRoute(
150178
opts: ParseAstOptions & {
151179
codeSplitGroupings: CodeSplitGroupings
@@ -411,7 +439,7 @@ export function compileCodeSplitReferenceRoute(
411439
LAZY_ROUTE_COMPONENT_IDENT,
412440
)
413441
) {
414-
programPath.unshiftContainer('body', [
442+
insertAfterDirectives(programPath, [
415443
template.statement(
416444
`import { ${LAZY_ROUTE_COMPONENT_IDENT} } from '${PACKAGE}'`,
417445
)(),
@@ -425,7 +453,7 @@ export function compileCodeSplitReferenceRoute(
425453
splitNodeMeta.localImporterIdent,
426454
)
427455
) {
428-
programPath.unshiftContainer('body', [
456+
insertAfterDirectives(programPath, [
429457
template.statement(
430458
`const ${splitNodeMeta.localImporterIdent} = () => import('${splitUrl}')`,
431459
)(),
@@ -479,8 +507,8 @@ export function compileCodeSplitReferenceRoute(
479507

480508
// Prepend the import statement to the program along with the importer function
481509
if (!hasImportedOrDefinedIdentifier(LAZY_FN_IDENT)) {
482-
programPath.unshiftContainer(
483-
'body',
510+
insertAfterDirectives(
511+
programPath,
484512
template.smart(
485513
`import { ${LAZY_FN_IDENT} } from '${PACKAGE}'`,
486514
)(),
@@ -494,7 +522,7 @@ export function compileCodeSplitReferenceRoute(
494522
splitNodeMeta.localImporterIdent,
495523
)
496524
) {
497-
programPath.unshiftContainer('body', [
525+
insertAfterDirectives(programPath, [
498526
template.statement(
499527
`const ${splitNodeMeta.localImporterIdent} = () => import('${splitUrl}')`,
500528
)(),
@@ -1100,10 +1128,12 @@ export function compileCodeSplitVirtualRoute(
11001128
removeSplitSearchParamFromFilename(opts.filename),
11011129
),
11021130
)
1103-
programPath.unshiftContainer('body', importDecl)
1131+
const importIndex = insertAfterDirectives(programPath, importDecl)
11041132

11051133
// Track imported identifiers for dead code elimination
1106-
const importPath = programPath.get('body')[0] as babel.NodePath
1134+
const importPath = programPath.get('body')[
1135+
importIndex
1136+
] as babel.NodePath
11071137
importPath.traverse({
11081138
Identifier(identPath) {
11091139
if (

0 commit comments

Comments
 (0)