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!