diff --git a/packages/vite/src/node/__tests__/filterRegex.spec.ts b/packages/vite/src/node/__tests__/filterRegex.spec.ts new file mode 100644 index 00000000000000..088c85d7018f0e --- /dev/null +++ b/packages/vite/src/node/__tests__/filterRegex.spec.ts @@ -0,0 +1,35 @@ +import { describe, expect, test } from 'vitest' +import { assetImportMetaUrlRE } from '../plugins/assetImportMetaUrl' +import { workerImportMetaUrlRE } from '../plugins/workerImportMetaUrl' + +describe('filter regexes do not cause catastrophic backtracking', () => { + const largeCode = + `new URL('https://example.com');\n`.repeat(200) + + `var a = 1;\n`.repeat(200_000) + + test('assetImportMetaUrlRE completes without backtracking on large files', () => { + assetImportMetaUrlRE.lastIndex = 0 + expect(assetImportMetaUrlRE.test(largeCode)).toBe(false) + }) + + test('workerImportMetaUrlRE completes without backtracking on large files', () => { + workerImportMetaUrlRE.lastIndex = 0 + expect(workerImportMetaUrlRE.test(largeCode)).toBe(false) + }) + + test('assetImportMetaUrlRE still matches valid patterns', () => { + assetImportMetaUrlRE.lastIndex = 0 + expect( + assetImportMetaUrlRE.test(`new URL('./asset.png', import.meta.url)`), + ).toBe(true) + }) + + test('workerImportMetaUrlRE still matches valid patterns', () => { + workerImportMetaUrlRE.lastIndex = 0 + expect( + workerImportMetaUrlRE.test( + `new Worker(new URL('./worker.js', import.meta.url))`, + ), + ).toBe(true) + }) +}) diff --git a/packages/vite/src/node/optimizer/rolldownDepPlugin.ts b/packages/vite/src/node/optimizer/rolldownDepPlugin.ts index d9356127695de4..90660b6e5fb145 100644 --- a/packages/vite/src/node/optimizer/rolldownDepPlugin.ts +++ b/packages/vite/src/node/optimizer/rolldownDepPlugin.ts @@ -22,6 +22,7 @@ import type { Environment } from '../environment' import { createBackCompatIdResolver } from '../idResolver' import { isWindows } from '../../shared/utils' import { hasViteIgnoreRE } from '../plugins/importAnalysis' +import { assetImportMetaUrlRE } from '../plugins/assetImportMetaUrl' const externalWithConversionNamespace = 'vite:dep-pre-bundle:external-conversion' @@ -313,16 +314,15 @@ export function rolldownDepPlugin( }, transform: { filter: { - code: /new\s+URL.+import\.meta\.url/s, + code: assetImportMetaUrlRE, }, async handler(code, id) { let s: MagicString | undefined - const assetImportMetaUrlRE = - /\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*(?:,\s*)?\)/dg + const re = new RegExp(assetImportMetaUrlRE) const cleanString = stripLiteral(code) let match: RegExpExecArray | null - while ((match = assetImportMetaUrlRE.exec(cleanString))) { + while ((match = re.exec(cleanString))) { const [[startIndex, endIndex], [urlStart, urlEnd]] = match.indices! if (hasViteIgnoreRE.test(code.slice(startIndex, urlStart))) continue diff --git a/packages/vite/src/node/plugins/assetImportMetaUrl.ts b/packages/vite/src/node/plugins/assetImportMetaUrl.ts index 803441509f5221..b28da927b0e523 100644 --- a/packages/vite/src/node/plugins/assetImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/assetImportMetaUrl.ts @@ -31,6 +31,9 @@ import { hasViteIgnoreRE } from './importAnalysis' * import.meta.glob('./dir/**.png', { eager: true, import: 'default' })[`./dir/${name}.png`] * ``` */ +export const assetImportMetaUrlRE: RegExp = + /\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*(?:,\s*)?\)/dg + export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { const { publicDir } = config let assetResolver: ResolveIdFn @@ -56,16 +59,15 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { id: { exclude: [exactRegex(preloadHelperId), exactRegex(CLIENT_ENTRY)], }, - code: /new\s+URL.+import\.meta\.url/s, + code: assetImportMetaUrlRE, }, async handler(code, id) { let s: MagicString | undefined - const assetImportMetaUrlRE = - /\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*(?:,\s*)?\)/dg + const re = new RegExp(assetImportMetaUrlRE) const cleanString = stripLiteral(code) let match: RegExpExecArray | null - while ((match = assetImportMetaUrlRE.exec(cleanString))) { + while ((match = re.exec(cleanString))) { const [[startIndex, endIndex], [urlStart, urlEnd]] = match.indices! if (hasViteIgnoreRE.test(code.slice(startIndex, urlStart))) continue diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index 27932b5df751d6..84c249f4829882 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -181,8 +181,8 @@ async function getWorkerType( return 'classic' } -const workerImportMetaUrlRE = - /new\s+(?:Worker|SharedWorker)\s*\(\s*new\s+URL.+?import\.meta\.url/s +export const workerImportMetaUrlRE: RegExp = + /\bnew\s+(?:Worker|SharedWorker)\s*\(\s*(new\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*(?:,\s*)?\))/dg export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { const isBundled = config.isBundled @@ -209,11 +209,10 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { async handler(code, id) { let s: MagicString | undefined const cleanString = stripLiteral(code) - const workerImportMetaUrlRE = - /\bnew\s+(?:Worker|SharedWorker)\s*\(\s*(new\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*(?:,\s*)?\))/dg + const re = new RegExp(workerImportMetaUrlRE) let match: RegExpExecArray | null - while ((match = workerImportMetaUrlRE.exec(cleanString))) { + while ((match = re.exec(cleanString))) { const [[, endIndex], [expStart, expEnd], [urlStart, urlEnd]] = match.indices!