-
-
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 1 commit
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,149 @@ | ||||||||||||||||||
| // 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 (true) { | ||||||||||||||||||
| 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; | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| 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.
l: This while loop could potentially run forever. Would it make sense to have some return at a specific index number? E.g.
Uh oh!
There was an error while loading. Please reload this page.
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.
Good point. I updated the loop to use an explicit
index < userCode.lengthexit condition instead ofwhile (true), and added a small EOF test to lock that behavior in.dd6204f
I didn't add a fixed index cap, since that could incorrectly stop on valid inputs with longer comments or multiple directives. The scanner still does a single forward pass over the input, so the runtime remains linear in the module length.