1- // Rollup doesn't like if we put the directive regex as a literal (?). No idea why.
2- /* oxlint-disable sdk/no-regexp-constructor */
3-
41import type { LoaderThis } from './types' ;
52
63export type ValueInjectionLoaderOptions = {
74 values : Record < string , unknown > ;
85} ;
96
10- // We need to be careful not to inject anything before any `"use strict";`s or "use client"s or really any other directive.
11- // As an additional complication directives may come after any number of comments.
12- // 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
13- export const SKIP_COMMENT_AND_DIRECTIVE_REGEX =
14- // Note: CodeQL complains that this regex potentially has n^2 runtime. This likely won't affect realistic files.
15- new RegExp ( '^(?:\\s*|/\\*(?:.|\\r|\\n)*?\\*/|//.*[\\n\\r])*(?:"[^"]*";?|\'[^\']*\';?)?' ) ;
7+ // We need to be careful not to inject anything before any `"use strict";`s or "use client"s or really any other
8+ // directives. A small scanner is easier to reason about than the previous regex and avoids regex backtracking concerns.
9+ export function findInjectionIndexAfterDirectives ( userCode : string ) : number {
10+ let index = 0 ;
11+ let lastDirectiveEndIndex : number | undefined ;
12+
13+ while ( true ) {
14+ const statementStartIndex = skipWhitespaceAndComments ( userCode , index ) ;
15+
16+ const nextDirectiveIndex = skipDirective ( userCode , statementStartIndex ) ;
17+ if ( nextDirectiveIndex === undefined ) {
18+ return lastDirectiveEndIndex ?? statementStartIndex ;
19+ }
20+
21+ const statementEndIndex = skipDirectiveTerminator ( userCode , nextDirectiveIndex ) ;
22+ if ( statementEndIndex === undefined ) {
23+ return lastDirectiveEndIndex ?? statementStartIndex ;
24+ }
25+
26+ index = statementEndIndex ;
27+ lastDirectiveEndIndex = statementEndIndex ;
28+ }
29+ }
30+
31+ function skipWhitespaceAndComments ( userCode : string , startIndex : number ) : number {
32+ let index = startIndex ;
33+
34+ while ( index < userCode . length ) {
35+ const char = userCode [ index ] ;
36+ const nextChar = userCode [ index + 1 ] ;
37+
38+ if ( char && / \s / . test ( char ) ) {
39+ index += 1 ;
40+ continue ;
41+ }
42+
43+ if ( char === '/' && nextChar === '/' ) {
44+ index += 2 ;
45+ while ( index < userCode . length && userCode [ index ] !== '\n' && userCode [ index ] !== '\r' ) {
46+ index += 1 ;
47+ }
48+ continue ;
49+ }
50+
51+ if ( char === '/' && nextChar === '*' ) {
52+ const commentEndIndex = userCode . indexOf ( '*/' , index + 2 ) ;
53+ if ( commentEndIndex === - 1 ) {
54+ return startIndex ;
55+ }
56+
57+ index = commentEndIndex + 2 ;
58+ continue ;
59+ }
60+
61+ return index ;
62+ }
63+
64+ return index ;
65+ }
66+
67+ function skipDirective ( userCode : string , startIndex : number ) : number | undefined {
68+ const quote = userCode [ startIndex ] ;
69+
70+ if ( quote !== '"' && quote !== "'" ) {
71+ return undefined ;
72+ }
73+
74+ let index = startIndex + 1 ;
75+
76+ while ( index < userCode . length ) {
77+ const char = userCode [ index ] ;
78+
79+ if ( char === '\\' ) {
80+ index += 2 ;
81+ continue ;
82+ }
83+
84+ if ( char === quote ) {
85+ index += 1 ;
86+ break ;
87+ }
88+
89+ if ( char === '\n' || char === '\r' ) {
90+ return undefined ;
91+ }
92+
93+ index += 1 ;
94+ }
95+
96+ if ( index > userCode . length || userCode [ index - 1 ] !== quote ) {
97+ return undefined ;
98+ }
99+
100+ return index ;
101+ }
102+
103+ function skipDirectiveTerminator ( userCode : string , startIndex : number ) : number | undefined {
104+ let index = startIndex ;
105+
106+ while ( index < userCode . length ) {
107+ const char = userCode [ index ] ;
108+ const nextChar = userCode [ index + 1 ] ;
109+
110+ if ( char === ';' ) {
111+ return index + 1 ;
112+ }
113+
114+ if ( char === '\n' || char === '\r' || char === '}' ) {
115+ return index ;
116+ }
117+
118+ if ( char && / \s / . test ( char ) ) {
119+ index += 1 ;
120+ continue ;
121+ }
122+
123+ if ( char === '/' && nextChar === '/' ) {
124+ return index ;
125+ }
126+
127+ if ( char === '/' && nextChar === '*' ) {
128+ const commentEndIndex = userCode . indexOf ( '*/' , index + 2 ) ;
129+ if ( commentEndIndex === - 1 ) {
130+ return undefined ;
131+ }
132+
133+ const comment = userCode . slice ( index + 2 , commentEndIndex ) ;
134+ if ( comment . includes ( '\n' ) || comment . includes ( '\r' ) ) {
135+ return index ;
136+ }
137+
138+ index = commentEndIndex + 2 ;
139+ continue ;
140+ }
141+
142+ return undefined ;
143+ }
144+
145+ return index ;
146+ }
16147
17148/**
18149 * Set values on the global/window object at the start of a module.
@@ -36,7 +167,6 @@ export default function valueInjectionLoader(this: LoaderThis<ValueInjectionLoad
36167 . map ( ( [ key , value ] ) => `globalThis["${ key } "] = ${ JSON . stringify ( value ) } ;` )
37168 . join ( '' ) ;
38169
39- return userCode . replace ( SKIP_COMMENT_AND_DIRECTIVE_REGEX , match => {
40- return match + injectedCode ;
41- } ) ;
170+ const injectionIndex = findInjectionIndexAfterDirectives ( userCode ) ;
171+ return `${ userCode . slice ( 0 , injectionIndex ) } ${ injectedCode } ${ userCode . slice ( injectionIndex ) } ` ;
42172}
0 commit comments