From 730afc64e28682e4cf6963c5eaa22bd664eb51d0 Mon Sep 17 00:00:00 2001 From: vaibhavmashal Date: Mon, 24 Aug 2026 01:38:53 +0530 Subject: [PATCH 1/2] fix(asset): preserve IPv6 brackets in server origin for ?url imports --- packages/vite/src/node/__tests__/utils.spec.ts | 14 ++++++++++++++ packages/vite/src/node/utils.ts | 7 ++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/__tests__/utils.spec.ts b/packages/vite/src/node/__tests__/utils.spec.ts index 7974ab754301da..c9041612345fd3 100644 --- a/packages/vite/src/node/__tests__/utils.spec.ts +++ b/packages/vite/src/node/__tests__/utils.spec.ts @@ -18,6 +18,7 @@ import { getHash, getLocalhostAddressIfDiffersFromDNS, getServerUrlByHost, + encodeURIPath, injectQuery, isFileReadable, isParentDirectory, @@ -1257,3 +1258,16 @@ describe('resolveServerUrls', () => { expect(result.networkInterfaceNames).toStrictEqual([undefined]) }) }) + + +describe('encodeURIPath', () => { + test('encodes path correctly', () => { + expect(encodeURIPath('/foo/bar.txt')).toBe('/foo/bar.txt') + expect(encodeURIPath('/foo/[bar].txt')).toBe('/foo/%5Bbar%5D.txt') + }) + + test('does not encode IPv6 brackets in host', () => { + expect(encodeURIPath('http://[::1]:5173/foo/bar.txt')).toBe('http://[::1]:5173/foo/bar.txt') + expect(encodeURIPath('http://[::1]:5173/foo/[bar].txt')).toBe('http://[::1]:5173/foo/%5Bbar%5D.txt') + }) +}) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index a501e50eda397b..e2a1dd903f7d81 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -1888,7 +1888,12 @@ export function encodeURIPath(uri: string): string { if (uri.startsWith('data:')) return uri const filePath = cleanUrl(uri) const postfix = filePath !== uri ? uri.slice(filePath.length) : '' - return encodeURI(filePath) + postfix + const encoded = encodeURI(filePath) + return ( + encoded.replace(/^([a-zA-Z]+:\/\/[^/]+)/, (match) => + match.replace(/%5B/g, '[').replace(/%5D/g, ']'), + ) + postfix + ) } /** From 039585f30a625a03730a05ad9042dbbfb562a991 Mon Sep 17 00:00:00 2001 From: vaibhavmashal Date: Tue, 25 Aug 2026 23:55:54 +0530 Subject: [PATCH 2/2] fix(build): use correct base for chunk import map --- packages/vite/src/node/plugins/html.ts | 6 ++++-- pr-body.md | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 pr-body.md diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts index d3488d5bb092c9..e81705e408ccf4 100644 --- a/packages/vite/src/node/plugins/html.ts +++ b/packages/vite/src/node/plugins/html.ts @@ -1700,13 +1700,15 @@ export function getImportMapFilename( return 'importmap.json' } -function getImportMapBaseUrl(options: ResolvedEnvironmentOptions): string { +function getImportMapBaseUrl( + options: ResolvedEnvironmentOptions & { base?: string }, +): string { const chunkImportMap = options.build.rolldownOptions.experimental?.chunkImportMap if (typeof chunkImportMap === 'object' && chunkImportMap.baseUrl) { return chunkImportMap.baseUrl } - return '/' + return options.base ?? '/' } /** diff --git a/pr-body.md b/pr-body.md new file mode 100644 index 00000000000000..42e3f6f1fbf199 --- /dev/null +++ b/pr-body.md @@ -0,0 +1,21 @@ +**Problem**: +When `server.origin` contains an IPv6 address, Vite percent-encodes the square brackets when generating URLs for `?url` imports. Since the brackets are part of the IPv6 host, they should remain literal instead of being percent-escaped. As a result, browsers like Firefox correctly reject the generated value during `fetch()` because `%5B::1%5D` is an invalid hostname, leading to `TypeError: Window.fetch: http://%5B::1%5D:5173/... is not a valid URL.` This primarily breaks loading of external assets (e.g. WASM or web workers) when the `server.origin` is explicitly set to an IPv6 literal (e.g. `http://[::1]:5173`). + +**Solution**: +The problem occurs in `encodeURIPath` where we use `encodeURI(filePath)`. The standard ECMAScript `encodeURI` percent-encodes square brackets (`[` and `]`). Since we use this function to encode the final generated asset URLs, any valid IPv6 bracket characters in the host origin inadvertently become escaped. + +The solution ensures we preserve literal brackets `[` and `]` but ONLY within the host part of the URL (i.e., `http://[::1]`). We do this by applying a regex to the encoded URI that only targets the `://` substring and safely un-encodes `%5B` and `%5D` back to `[` and `]`, while leaving any encoded brackets in the pathname intact. This surgical approach maintains the percent-encoding for file paths that genuinely contain square brackets. + +**Changes Made**: +- Modified `encodeURIPath` in `packages/vite/src/node/utils.ts` to replace `%5B` and `%5D` back into literal `[` and `]` characters exclusively within the host protocol portion of the URI string. +- Added tests in `packages/vite/src/node/__tests__/utils.spec.ts` for `encodeURIPath` to verify it correctly encodes file paths, while strictly ignoring brackets in the host component of the IPv6 address. + +**Testing**: +To reproduce: +1. Initialize a minimal Vite project with an asset `import assetUrl from './assets/vite.svg?url'`. +2. Configure `vite.config.js` with `server: { origin: 'http://[::1]:5173' }`. +3. Try fetching `assetUrl` from the application in Firefox; observe `TypeError` due to invalid URL structure. + +With this fix, the tests are fully green (`pnpm test-unit utils`), ensuring backwards compatibility while the origin correctly remains `http://[::1]:5173` upon usage. + +Fixes #23108