Skip to content

Commit 781171a

Browse files
committed
fix(transform): handle leading comments before use client/server directive
The preamble insertion regex only matched "use client"/"use server" at the start of the file. Files with leading comments (e.g. biome-ignore pragmas) before the directive caused the preamble to be prepended, pushing the directive down and violating Next.js/Turbopack's requirement that it be the first expression. Widen the regex to skip leading line and block comments before matching the directive.
1 parent 2c2c95f commit 781171a

2 files changed

Lines changed: 34 additions & 4 deletions

File tree

packages/domscribe-transform/src/plugins/turbopack/turbopack.loader.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,35 @@ describe('Turbopack Loader', () => {
462462
expect(directiveIndex).toBe(0);
463463
});
464464

465+
it('should inject preamble after use client with leading comments', async () => {
466+
// Arrange — files with lint-ignore comments before "use client"
467+
const source =
468+
'/** biome-ignore-all */\n/** another comment */\n"use client";\nimport React from "react";';
469+
const context = createLoaderContext('/test/InputGroup.tsx');
470+
const asyncCallback = vi.fn();
471+
context.async = vi.fn(() => asyncCallback);
472+
mockInjector.inject.mockReturnValue(
473+
createMockInjectorResult(
474+
'/** biome-ignore-all */\n/** another comment */\n"use client";\ntransformed',
475+
1,
476+
),
477+
);
478+
479+
// Act
480+
loaderModule.default.call(context, source);
481+
await vi.waitFor(() => expect(asyncCallback).toHaveBeenCalled());
482+
483+
// Assert — comments + directive must come before the preamble
484+
const outputCode = asyncCallback.mock.calls[0][1] as string;
485+
const directiveIndex = outputCode.indexOf('"use client"');
486+
const preambleIndex = outputCode.indexOf('__DOMSCRIBE_RELAY_PORT__');
487+
expect(directiveIndex).toBeLessThan(preambleIndex);
488+
// Comments should be preserved before the directive
489+
expect(outputCode.indexOf('biome-ignore-all')).toBeLessThan(
490+
directiveIndex,
491+
);
492+
});
493+
465494
it('should inject client globals after use server directive', async () => {
466495
// Arrange
467496
const source = '"use server";\nexport async function action() {}';

packages/domscribe-transform/src/plugins/turbopack/turbopack.loader.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -414,13 +414,14 @@ async function transformWithInit(
414414
if (preamble) {
415415
// Insert after 'use client'/'use server' directive so we don't
416416
// violate Next.js/Turbopack's requirement that it be the first expression.
417+
// The regex allows leading comments (line or block) before the directive,
418+
// which is valid JS and common in codebases using lint-ignore pragmas.
417419
const directiveMatch = transformedCode.match(
418-
/^(['"])use (client|server)\1;?[ \t]*\r?\n?/,
420+
/^(?:[ \t]*(?:\/\/[^\n]*|\/\*[\s\S]*?\*\/)[ \t]*\r?\n)*[ \t]*(['"])use (client|server)\1;?[ \t]*\r?\n?/,
419421
);
420422
if (directiveMatch) {
421-
const directive = directiveMatch[0];
422-
outputCode =
423-
directive + preamble + transformedCode.slice(directive.length);
423+
const prologue = directiveMatch[0];
424+
outputCode = prologue + preamble + transformedCode.slice(prologue.length);
424425
} else {
425426
outputCode = preamble + transformedCode;
426427
}

0 commit comments

Comments
 (0)