Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions packages/vite/src/node/__tests__/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
getHash,
getLocalhostAddressIfDiffersFromDNS,
getServerUrlByHost,
encodeURIPath,
injectQuery,
isFileReadable,
isParentDirectory,
Expand Down Expand Up @@ -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')
})
})
6 changes: 4 additions & 2 deletions packages/vite/src/node/plugins/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? '/'
}

/**
Expand Down
7 changes: 6 additions & 1 deletion packages/vite/src/node/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
}

/**
Expand Down
21 changes: 21 additions & 0 deletions pr-body.md
Original file line number Diff line number Diff line change
@@ -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 `<protocol>://<host>` 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
Loading