-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
fix(nextjs): preserve directive prologues in turbopack loaders #20103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 2 commits
a648434
dd6204f
81516b3
c1d0518
215d6bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,18 +1,151 @@ | ||||||||||||||||||
| // Rollup doesn't like if we put the directive regex as a literal (?). No idea why. | ||||||||||||||||||
| /* oxlint-disable sdk/no-regexp-constructor */ | ||||||||||||||||||
|
|
||||||||||||||||||
| import type { LoaderThis } from './types'; | ||||||||||||||||||
|
|
||||||||||||||||||
| export type ValueInjectionLoaderOptions = { | ||||||||||||||||||
| values: Record<string, unknown>; | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| // We need to be careful not to inject anything before any `"use strict";`s or "use client"s or really any other directive. | ||||||||||||||||||
| // As an additional complication directives may come after any number of comments. | ||||||||||||||||||
| // This regex is shamelessly stolen from: https://github.com/getsentry/sentry-javascript-bundler-plugins/blob/7f984482c73e4284e8b12a08dfedf23b5a82f0af/packages/bundler-plugin-core/src/index.ts#L535-L539 | ||||||||||||||||||
| export const SKIP_COMMENT_AND_DIRECTIVE_REGEX = | ||||||||||||||||||
| // Note: CodeQL complains that this regex potentially has n^2 runtime. This likely won't affect realistic files. | ||||||||||||||||||
| new RegExp('^(?:\\s*|/\\*(?:.|\\r|\\n)*?\\*/|//.*[\\n\\r])*(?:"[^"]*";?|\'[^\']*\';?)?'); | ||||||||||||||||||
| // We need to be careful not to inject anything before any `"use strict";`s or "use client"s or really any other | ||||||||||||||||||
| // directives. A small scanner is easier to reason about than the previous regex and avoids regex backtracking concerns. | ||||||||||||||||||
| export function findInjectionIndexAfterDirectives(userCode: string): number { | ||||||||||||||||||
| let index = 0; | ||||||||||||||||||
| let lastDirectiveEndIndex: number | undefined; | ||||||||||||||||||
|
|
||||||||||||||||||
| while (index < userCode.length) { | ||||||||||||||||||
| const statementStartIndex = skipWhitespaceAndComments(userCode, index); | ||||||||||||||||||
|
|
||||||||||||||||||
| const nextDirectiveIndex = skipDirective(userCode, statementStartIndex); | ||||||||||||||||||
| if (nextDirectiveIndex === undefined) { | ||||||||||||||||||
| return lastDirectiveEndIndex ?? statementStartIndex; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| const statementEndIndex = skipDirectiveTerminator(userCode, nextDirectiveIndex); | ||||||||||||||||||
| if (statementEndIndex === undefined) { | ||||||||||||||||||
| return lastDirectiveEndIndex ?? statementStartIndex; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| index = statementEndIndex; | ||||||||||||||||||
| lastDirectiveEndIndex = statementEndIndex; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| return lastDirectiveEndIndex ?? index; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| function skipWhitespaceAndComments(userCode: string, startIndex: number): number { | ||||||||||||||||||
| let index = startIndex; | ||||||||||||||||||
|
|
||||||||||||||||||
| while (index < userCode.length) { | ||||||||||||||||||
| const char = userCode[index]; | ||||||||||||||||||
| const nextChar = userCode[index + 1]; | ||||||||||||||||||
|
|
||||||||||||||||||
| if (char && /\s/.test(char)) { | ||||||||||||||||||
| index += 1; | ||||||||||||||||||
| continue; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| if (char === '/' && nextChar === '/') { | ||||||||||||||||||
|
||||||||||||||||||
| index += 2; | ||||||||||||||||||
| while (index < userCode.length && userCode[index] !== '\n' && userCode[index] !== '\r') { | ||||||||||||||||||
| index += 1; | ||||||||||||||||||
| } | ||||||||||||||||||
| continue; | ||||||||||||||||||
|
||||||||||||||||||
| index += 2; | |
| while (index < userCode.length && userCode[index] !== '\n' && userCode[index] !== '\r') { | |
| index += 1; | |
| } | |
| continue; | |
| const newline = code.indexOf('\n', i + 2); | |
| i = newline === -1 ? code.length : newline + 1; | |
| continue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or even shorter (but maybe less readable):
if (code.startsWith('//', i)) {
i = code.indexOf('\n', i);
if (i === -1) break;
continue;
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, I simplified the line-comment handling here to use indexOf('\n', index + 2), which makes this path shorter and easier to read.
Applied in c1d0518.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need three functions here (as all of them are doing similar things). The only thing we want to find is the correct place for injection.
What do you think of something like this? This would just return the number after the last directive. But maybe I am missing something here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, this direction makes sense.
I refactored the directive scan so the logic now lives in a single scanner flow instead of being split across three helpers. I kept only a small string-literal helper so we still handle escaped/unterminated quote cases correctly.
Applied in c1d0518.