From cc73a231abafecb5b0b2bb84fc1fa15987c30d3d Mon Sep 17 00:00:00 2001 From: ChrisPan <39005916+szupzj18@users.noreply.github.com> Date: Thu, 27 Aug 2026 19:31:54 +0800 Subject: [PATCH] fix(config): don't unescape glob characters in Windows input paths --- .../vite/src/node/__tests__/config.spec.ts | 57 ++++++++++++++++++- packages/vite/src/node/config.ts | 7 ++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/packages/vite/src/node/__tests__/config.spec.ts b/packages/vite/src/node/__tests__/config.spec.ts index 589602dc0df54d..5ae43d4634a2f5 100644 --- a/packages/vite/src/node/__tests__/config.spec.ts +++ b/packages/vite/src/node/__tests__/config.spec.ts @@ -1249,7 +1249,9 @@ describe('resolveConfig', () => { } }) - test('support escaped input', async () => { + // On Windows, backslash is the path separator, so glob characters cannot + // be escaped with a backslash in input paths. + test.skipIf(isWindows)('support escaped input', async () => { const cases: { name: string input: UserConfig['input'] @@ -1314,6 +1316,59 @@ describe('resolveConfig', () => { } }) + // https://github.com/vitejs/vite/issues/23383 + test('preserves Windows input paths with glob-special segment names', async () => { + const cases: { + name: string + input: UserConfig['input'] + expected: UserConfig['input'] + }[] = [ + ...(isWindows + ? [ + { + name: 'segment starting with @', + input: 'D:\\desk\\test\\@src\\whatever.html', + expected: 'D:\\desk\\test\\@src\\whatever.html', + }, + { + name: 'segment starting with !', + input: 'D:\\desk\\test\\!notes\\page.html', + expected: 'D:\\desk\\test\\!notes\\page.html', + }, + { + name: 'segment starting with +', + input: 'D:\\desk\\test\\+draft\\page.html', + expected: 'D:\\desk\\test\\+draft\\page.html', + }, + { + name: 'segment starting with (', + input: 'D:\\desk\\test\\(draft)\\page.html', + expected: 'D:\\desk\\test\\(draft)\\page.html', + }, + { + name: 'segment starting with [', + input: 'D:\\desk\\test\\[id]\\page.html', + expected: 'D:\\desk\\test\\[id]\\page.html', + }, + { + name: 'relative path', + input: 'test\\@src\\whatever.html', + expected: 'test\\@src\\whatever.html', + }, + ] + : []), + ] + + for (const { name, input, expected } of cases) { + await expect( + resolveConfig({ input }, 'serve'), + name, + ).resolves.toMatchObject({ + input: expected, + }) + } + }) + test('is used as the default for build.lib.entry when entry is omitted', async () => { const config = await resolveConfig( { diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index e350b1c3e82d76..35f615b135e5ca 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -33,7 +33,7 @@ import { createImportMetaResolver, importMetaResolveWithCustomHookString, } from '../module-runner/importMetaResolver' -import { withTrailingSlash } from '../shared/utils' +import { isWindows, withTrailingSlash } from '../shared/utils' import type { AnymatchFn } from '../types/anymatch' import type { HtmlAssetSource } from './assetSource' import { PartialEnvironment } from './baseEnvironment' @@ -955,6 +955,11 @@ function unescapeGlobCharacters(value: string): string { `so the ${JSON.stringify(value)} is not allowed. Please escape them with a backslash (\\)`, ) } + // On Windows, backslash is the path separator, so it must not be treated + // as an escape character for glob characters (#23383). + if (isWindows) { + return value + } // unescape glob characters return value.replace(escapedGlobCharactersRE, '$1') }