From 5d8e4d0283c3160f60175595f856620657731f5d Mon Sep 17 00:00:00 2001 From: o-m12a Date: Tue, 10 Mar 2026 00:26:21 +0900 Subject: [PATCH 1/6] fix(optimizer): use precise regexes for transform filter to avoid backtracking The filter.code regexes in workerImportMetaUrl, assetImportMetaUrl, and rolldownDepPlugin used `.+` with the `/s` flag, which caused catastrophic backtracking on large files containing `new URL()` without `import.meta.url`. Replace them with the precise regexes that require a string literal argument, matching the patterns already used in the transform handlers. This allows the regex to fail fast on non-matching input. Closes #21696 Co-Authored-By: Claude Opus 4.6 --- .../src/node/__tests__/filterRegex.spec.ts | 50 +++++++++++++++++++ .../src/node/optimizer/rolldownDepPlugin.ts | 3 +- .../src/node/plugins/assetImportMetaUrl.ts | 5 +- .../src/node/plugins/workerImportMetaUrl.ts | 4 +- 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 packages/vite/src/node/__tests__/filterRegex.spec.ts 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..efceea45806cf0 --- /dev/null +++ b/packages/vite/src/node/__tests__/filterRegex.spec.ts @@ -0,0 +1,50 @@ +import { describe, expect, test } from 'vitest' +import { assetImportMetaUrlFilterRE } from '../plugins/assetImportMetaUrl' +import { workerImportMetaUrlRE } from '../plugins/workerImportMetaUrl' + +// These are the filter.code regexes used in the transform hooks. +// They must not cause catastrophic backtracking on large files. +// See https://github.com/vitejs/vite/issues/21696 + +describe('filter regexes do not cause catastrophic backtracking', () => { + // Large file where `new URL(...)` appears many times but `import.meta.url` + // is absent. With the old /s + .+ pattern, each `new URL` occurrence would + // cause the regex engine to consume the rest of the file before backtracking. + const largeCode = + `new URL('https://example.com');\n`.repeat(200) + + `var a = 1;\n`.repeat(200_000) + + test('assetImportMetaUrlFilterRE completes in reasonable time on large files', () => { + const start = performance.now() + const result = assetImportMetaUrlFilterRE.test(largeCode) + const duration = performance.now() - start + + expect(result).toBe(false) + expect(duration).toBeLessThan(1000) + }) + + test('workerImportMetaUrlRE completes in reasonable time on large files', () => { + const start = performance.now() + const result = workerImportMetaUrlRE.test(largeCode) + const duration = performance.now() - start + + expect(result).toBe(false) + expect(duration).toBeLessThan(1000) + }) + + test('assetImportMetaUrlFilterRE still matches valid patterns', () => { + expect( + assetImportMetaUrlFilterRE.test( + `new URL('./asset.png', import.meta.url)`, + ), + ).toBe(true) + }) + + test('workerImportMetaUrlRE still matches valid patterns', () => { + 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..76ad6fa5968f26 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 { assetImportMetaUrlFilterRE } from '../plugins/assetImportMetaUrl' const externalWithConversionNamespace = 'vite:dep-pre-bundle:external-conversion' @@ -313,7 +314,7 @@ export function rolldownDepPlugin( }, transform: { filter: { - code: /new\s+URL.+import\.meta\.url/s, + code: assetImportMetaUrlFilterRE, }, async handler(code, id) { let s: MagicString | undefined diff --git a/packages/vite/src/node/plugins/assetImportMetaUrl.ts b/packages/vite/src/node/plugins/assetImportMetaUrl.ts index 803441509f5221..90ab8113307011 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 assetImportMetaUrlFilterRE: RegExp = + /\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url/ + export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { const { publicDir } = config let assetResolver: ResolveIdFn @@ -56,7 +59,7 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { id: { exclude: [exactRegex(preloadHelperId), exactRegex(CLIENT_ENTRY)], }, - code: /new\s+URL.+import\.meta\.url/s, + code: assetImportMetaUrlFilterRE, }, async handler(code, id) { let s: MagicString | undefined diff --git a/packages/vite/src/node/plugins/workerImportMetaUrl.ts b/packages/vite/src/node/plugins/workerImportMetaUrl.ts index 27932b5df751d6..f79d1cc66fd684 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/ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { const isBundled = config.isBundled From b5a6019e4d2caa452975a1a97c7012932a1c9e5f Mon Sep 17 00:00:00 2001 From: o-m12a Date: Tue, 10 Mar 2026 00:39:10 +0900 Subject: [PATCH 2/6] test: remove time-based assertions to avoid flaky tests Replace `expect(duration).toBeLessThan(1000)` with relying on the default test timeout to catch backtracking regressions. This avoids flaky failures on slow CI runners. Co-Authored-By: Claude Opus 4.6 --- .../src/node/__tests__/filterRegex.spec.ts | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/packages/vite/src/node/__tests__/filterRegex.spec.ts b/packages/vite/src/node/__tests__/filterRegex.spec.ts index efceea45806cf0..f514212c69a2c1 100644 --- a/packages/vite/src/node/__tests__/filterRegex.spec.ts +++ b/packages/vite/src/node/__tests__/filterRegex.spec.ts @@ -14,22 +14,14 @@ describe('filter regexes do not cause catastrophic backtracking', () => { `new URL('https://example.com');\n`.repeat(200) + `var a = 1;\n`.repeat(200_000) - test('assetImportMetaUrlFilterRE completes in reasonable time on large files', () => { - const start = performance.now() - const result = assetImportMetaUrlFilterRE.test(largeCode) - const duration = performance.now() - start - - expect(result).toBe(false) - expect(duration).toBeLessThan(1000) + // These tests rely on the default test timeout (5s) to catch backtracking. + // The old regexes took >1s on this input; the new ones complete in ~3ms. + test('assetImportMetaUrlFilterRE completes without backtracking on large files', () => { + expect(assetImportMetaUrlFilterRE.test(largeCode)).toBe(false) }) - test('workerImportMetaUrlRE completes in reasonable time on large files', () => { - const start = performance.now() - const result = workerImportMetaUrlRE.test(largeCode) - const duration = performance.now() - start - - expect(result).toBe(false) - expect(duration).toBeLessThan(1000) + test('workerImportMetaUrlRE completes without backtracking on large files', () => { + expect(workerImportMetaUrlRE.test(largeCode)).toBe(false) }) test('assetImportMetaUrlFilterRE still matches valid patterns', () => { From c140266cf25cdf17b8b85093c6951e8036060be9 Mon Sep 17 00:00:00 2001 From: o-m12a Date: Tue, 10 Mar 2026 18:32:56 +0900 Subject: [PATCH 3/6] test: remove redundant comments from filter regex tests Co-Authored-By: Claude Opus 4.6 --- packages/vite/src/node/__tests__/filterRegex.spec.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packages/vite/src/node/__tests__/filterRegex.spec.ts b/packages/vite/src/node/__tests__/filterRegex.spec.ts index f514212c69a2c1..a54f612e5562e8 100644 --- a/packages/vite/src/node/__tests__/filterRegex.spec.ts +++ b/packages/vite/src/node/__tests__/filterRegex.spec.ts @@ -2,20 +2,11 @@ import { describe, expect, test } from 'vitest' import { assetImportMetaUrlFilterRE } from '../plugins/assetImportMetaUrl' import { workerImportMetaUrlRE } from '../plugins/workerImportMetaUrl' -// These are the filter.code regexes used in the transform hooks. -// They must not cause catastrophic backtracking on large files. -// See https://github.com/vitejs/vite/issues/21696 - describe('filter regexes do not cause catastrophic backtracking', () => { - // Large file where `new URL(...)` appears many times but `import.meta.url` - // is absent. With the old /s + .+ pattern, each `new URL` occurrence would - // cause the regex engine to consume the rest of the file before backtracking. const largeCode = `new URL('https://example.com');\n`.repeat(200) + `var a = 1;\n`.repeat(200_000) - // These tests rely on the default test timeout (5s) to catch backtracking. - // The old regexes took >1s on this input; the new ones complete in ~3ms. test('assetImportMetaUrlFilterRE completes without backtracking on large files', () => { expect(assetImportMetaUrlFilterRE.test(largeCode)).toBe(false) }) From e9bdaa3d100fe51e7cefb9f667ddaf8c82c4a5f4 Mon Sep 17 00:00:00 2001 From: o-m12a Date: Wed, 11 Mar 2026 10:48:46 +0900 Subject: [PATCH 4/6] refactor: unify filter and handler regexes Co-Authored-By: Claude Opus 4.6 --- .../src/node/__tests__/filterRegex.spec.ts | 28 +++++++++---------- .../src/node/optimizer/rolldownDepPlugin.ts | 9 +++--- .../src/node/plugins/assetImportMetaUrl.ts | 11 ++++---- .../src/node/plugins/workerImportMetaUrl.ts | 7 ++--- 4 files changed, 25 insertions(+), 30 deletions(-) diff --git a/packages/vite/src/node/__tests__/filterRegex.spec.ts b/packages/vite/src/node/__tests__/filterRegex.spec.ts index a54f612e5562e8..ecc695b0da0657 100644 --- a/packages/vite/src/node/__tests__/filterRegex.spec.ts +++ b/packages/vite/src/node/__tests__/filterRegex.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from 'vitest' -import { assetImportMetaUrlFilterRE } from '../plugins/assetImportMetaUrl' +import { assetImportMetaUrlRE } from '../plugins/assetImportMetaUrl' import { workerImportMetaUrlRE } from '../plugins/workerImportMetaUrl' describe('filter regexes do not cause catastrophic backtracking', () => { @@ -7,27 +7,25 @@ describe('filter regexes do not cause catastrophic backtracking', () => { `new URL('https://example.com');\n`.repeat(200) + `var a = 1;\n`.repeat(200_000) - test('assetImportMetaUrlFilterRE completes without backtracking on large files', () => { - expect(assetImportMetaUrlFilterRE.test(largeCode)).toBe(false) + test('assetImportMetaUrlRE completes without backtracking on large files', () => { + const re = new RegExp(assetImportMetaUrlRE) + expect(re.test(largeCode)).toBe(false) }) test('workerImportMetaUrlRE completes without backtracking on large files', () => { - expect(workerImportMetaUrlRE.test(largeCode)).toBe(false) + const re = new RegExp(workerImportMetaUrlRE) + expect(re.test(largeCode)).toBe(false) }) - test('assetImportMetaUrlFilterRE still matches valid patterns', () => { - expect( - assetImportMetaUrlFilterRE.test( - `new URL('./asset.png', import.meta.url)`, - ), - ).toBe(true) + test('assetImportMetaUrlRE still matches valid patterns', () => { + const re = new RegExp(assetImportMetaUrlRE) + expect(re.test(`new URL('./asset.png', import.meta.url)`)).toBe(true) }) test('workerImportMetaUrlRE still matches valid patterns', () => { - expect( - workerImportMetaUrlRE.test( - `new Worker(new URL('./worker.js', import.meta.url))`, - ), - ).toBe(true) + const re = new RegExp(workerImportMetaUrlRE) + expect(re.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 76ad6fa5968f26..90660b6e5fb145 100644 --- a/packages/vite/src/node/optimizer/rolldownDepPlugin.ts +++ b/packages/vite/src/node/optimizer/rolldownDepPlugin.ts @@ -22,7 +22,7 @@ import type { Environment } from '../environment' import { createBackCompatIdResolver } from '../idResolver' import { isWindows } from '../../shared/utils' import { hasViteIgnoreRE } from '../plugins/importAnalysis' -import { assetImportMetaUrlFilterRE } from '../plugins/assetImportMetaUrl' +import { assetImportMetaUrlRE } from '../plugins/assetImportMetaUrl' const externalWithConversionNamespace = 'vite:dep-pre-bundle:external-conversion' @@ -314,16 +314,15 @@ export function rolldownDepPlugin( }, transform: { filter: { - code: assetImportMetaUrlFilterRE, + 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 90ab8113307011..b28da927b0e523 100644 --- a/packages/vite/src/node/plugins/assetImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/assetImportMetaUrl.ts @@ -31,8 +31,8 @@ import { hasViteIgnoreRE } from './importAnalysis' * import.meta.glob('./dir/**.png', { eager: true, import: 'default' })[`./dir/${name}.png`] * ``` */ -export const assetImportMetaUrlFilterRE: RegExp = - /\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url/ +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 @@ -59,16 +59,15 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { id: { exclude: [exactRegex(preloadHelperId), exactRegex(CLIENT_ENTRY)], }, - code: assetImportMetaUrlFilterRE, + 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 f79d1cc66fd684..84c249f4829882 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -182,7 +182,7 @@ async function getWorkerType( } export const workerImportMetaUrlRE: RegExp = - /\bnew\s+(?:Worker|SharedWorker)\s*\(\s*new\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url/ + /\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! From dff8dda10c6c8860fa6d3047dc27d85299b2c698 Mon Sep 17 00:00:00 2001 From: o-m12a Date: Wed, 11 Mar 2026 12:56:05 +0900 Subject: [PATCH 5/6] refactor: use lastIndex reset instead of regex copy Co-Authored-By: Claude Opus 4.6 --- .../src/node/__tests__/filterRegex.spec.ts | 24 +++++++++++-------- .../src/node/optimizer/rolldownDepPlugin.ts | 4 ++-- .../src/node/plugins/assetImportMetaUrl.ts | 4 ++-- .../src/node/plugins/workerImportMetaUrl.ts | 4 ++-- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/vite/src/node/__tests__/filterRegex.spec.ts b/packages/vite/src/node/__tests__/filterRegex.spec.ts index ecc695b0da0657..088c85d7018f0e 100644 --- a/packages/vite/src/node/__tests__/filterRegex.spec.ts +++ b/packages/vite/src/node/__tests__/filterRegex.spec.ts @@ -8,24 +8,28 @@ describe('filter regexes do not cause catastrophic backtracking', () => { `var a = 1;\n`.repeat(200_000) test('assetImportMetaUrlRE completes without backtracking on large files', () => { - const re = new RegExp(assetImportMetaUrlRE) - expect(re.test(largeCode)).toBe(false) + assetImportMetaUrlRE.lastIndex = 0 + expect(assetImportMetaUrlRE.test(largeCode)).toBe(false) }) test('workerImportMetaUrlRE completes without backtracking on large files', () => { - const re = new RegExp(workerImportMetaUrlRE) - expect(re.test(largeCode)).toBe(false) + workerImportMetaUrlRE.lastIndex = 0 + expect(workerImportMetaUrlRE.test(largeCode)).toBe(false) }) test('assetImportMetaUrlRE still matches valid patterns', () => { - const re = new RegExp(assetImportMetaUrlRE) - expect(re.test(`new URL('./asset.png', import.meta.url)`)).toBe(true) + assetImportMetaUrlRE.lastIndex = 0 + expect( + assetImportMetaUrlRE.test(`new URL('./asset.png', import.meta.url)`), + ).toBe(true) }) test('workerImportMetaUrlRE still matches valid patterns', () => { - const re = new RegExp(workerImportMetaUrlRE) - expect(re.test(`new Worker(new URL('./worker.js', import.meta.url))`)).toBe( - true, - ) + 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 90660b6e5fb145..a4d5e4140adb2e 100644 --- a/packages/vite/src/node/optimizer/rolldownDepPlugin.ts +++ b/packages/vite/src/node/optimizer/rolldownDepPlugin.ts @@ -318,11 +318,11 @@ export function rolldownDepPlugin( }, async handler(code, id) { let s: MagicString | undefined - const re = new RegExp(assetImportMetaUrlRE) + assetImportMetaUrlRE.lastIndex = 0 const cleanString = stripLiteral(code) let match: RegExpExecArray | null - while ((match = re.exec(cleanString))) { + while ((match = assetImportMetaUrlRE.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 b28da927b0e523..ebb99909af9429 100644 --- a/packages/vite/src/node/plugins/assetImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/assetImportMetaUrl.ts @@ -63,11 +63,11 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { }, async handler(code, id) { let s: MagicString | undefined - const re = new RegExp(assetImportMetaUrlRE) + assetImportMetaUrlRE.lastIndex = 0 const cleanString = stripLiteral(code) let match: RegExpExecArray | null - while ((match = re.exec(cleanString))) { + while ((match = assetImportMetaUrlRE.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 84c249f4829882..81e28cb7588f90 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -209,10 +209,10 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { async handler(code, id) { let s: MagicString | undefined const cleanString = stripLiteral(code) - const re = new RegExp(workerImportMetaUrlRE) + workerImportMetaUrlRE.lastIndex = 0 let match: RegExpExecArray | null - while ((match = re.exec(cleanString))) { + while ((match = workerImportMetaUrlRE.exec(cleanString))) { const [[, endIndex], [expStart, expEnd], [urlStart, urlEnd]] = match.indices! From d2cae37bc8a240f715c97a3190045dd7d5c714fb Mon Sep 17 00:00:00 2001 From: o-m12a Date: Wed, 11 Mar 2026 13:05:32 +0900 Subject: [PATCH 6/6] refactor: revert to new RegExp() for async handler loops lastIndex = 0 doesn't work for async handlers with await inside the exec loop, as concurrent handler invocations can overwrite the shared lastIndex. Co-Authored-By: Claude Opus 4.6 --- packages/vite/src/node/optimizer/rolldownDepPlugin.ts | 4 ++-- packages/vite/src/node/plugins/assetImportMetaUrl.ts | 4 ++-- packages/vite/src/node/plugins/workerImportMetaUrl.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/vite/src/node/optimizer/rolldownDepPlugin.ts b/packages/vite/src/node/optimizer/rolldownDepPlugin.ts index a4d5e4140adb2e..90660b6e5fb145 100644 --- a/packages/vite/src/node/optimizer/rolldownDepPlugin.ts +++ b/packages/vite/src/node/optimizer/rolldownDepPlugin.ts @@ -318,11 +318,11 @@ export function rolldownDepPlugin( }, async handler(code, id) { let s: MagicString | undefined - assetImportMetaUrlRE.lastIndex = 0 + 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 ebb99909af9429..b28da927b0e523 100644 --- a/packages/vite/src/node/plugins/assetImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/assetImportMetaUrl.ts @@ -63,11 +63,11 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { }, async handler(code, id) { let s: MagicString | undefined - assetImportMetaUrlRE.lastIndex = 0 + 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 81e28cb7588f90..84c249f4829882 100644 --- a/packages/vite/src/node/plugins/workerImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/workerImportMetaUrl.ts @@ -209,10 +209,10 @@ export function workerImportMetaUrlPlugin(config: ResolvedConfig): Plugin { async handler(code, id) { let s: MagicString | undefined const cleanString = stripLiteral(code) - workerImportMetaUrlRE.lastIndex = 0 + 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!