diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index 7e37ac71ad1b6f..39b3e4fb15acf4 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -40,7 +40,7 @@ import { emptyDir, joinUrlSegments, normalizePath, - partialEncodeURI, + partialEncodeURIPath, requireResolveFromRootWithFallback, } from './utils' import { manifestPlugin } from './plugins/manifest' @@ -1093,7 +1093,7 @@ const getResolveUrl = (path: string, URL = 'URL') => `new ${URL}(${path}).href` const getRelativeUrlFromDocument = (relativePath: string, umd = false) => getResolveUrl( - `'${escapeId(partialEncodeURI(relativePath))}', ${ + `'${escapeId(partialEncodeURIPath(relativePath))}', ${ umd ? `typeof document === 'undefined' ? location.href : ` : '' }document.currentScript && document.currentScript.src || document.baseURI`, ) @@ -1120,13 +1120,13 @@ const relativeUrlMechanisms: Record< )} : ${getRelativeUrlFromDocument(relativePath)})`, es: (relativePath) => getResolveUrl( - `'${escapeId(partialEncodeURI(relativePath))}', import.meta.url`, + `'${escapeId(partialEncodeURIPath(relativePath))}', import.meta.url`, ), iife: (relativePath) => getRelativeUrlFromDocument(relativePath), // NOTE: make sure rollup generate `module` params system: (relativePath) => getResolveUrl( - `'${escapeId(partialEncodeURI(relativePath))}', module.meta.url`, + `'${escapeId(partialEncodeURIPath(relativePath))}', module.meta.url`, ), umd: (relativePath) => `(typeof document === 'undefined' && typeof location === 'undefined' ? ${getFileUrlFromRelativePath( @@ -1139,7 +1139,7 @@ const customRelativeUrlMechanisms = { ...relativeUrlMechanisms, 'worker-iife': (relativePath) => getResolveUrl( - `'${escapeId(partialEncodeURI(relativePath))}', self.location.href`, + `'${escapeId(partialEncodeURIPath(relativePath))}', self.location.href`, ), } as const satisfies Record string> diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index e867bd1ace2b80..e765a911ce0766 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -18,6 +18,7 @@ import type { Plugin } from '../plugin' import type { ResolvedConfig } from '../config' import { checkPublicFile } from '../publicDir' import { + encodeURIPath, getHash, injectQuery, joinUrlSegments, @@ -100,7 +101,7 @@ export function renderAssetUrlInJS( ) const replacementString = typeof replacement === 'string' - ? JSON.stringify(encodeURI(replacement)).slice(1, -1) + ? JSON.stringify(encodeURIPath(replacement)).slice(1, -1) : `"+${replacement.runtime}+"` s.update(match.index, match.index + full.length, replacementString) } @@ -123,7 +124,7 @@ export function renderAssetUrlInJS( ) const replacementString = typeof replacement === 'string' - ? JSON.stringify(encodeURI(replacement)).slice(1, -1) + ? JSON.stringify(encodeURIPath(replacement)).slice(1, -1) : `"+${replacement.runtime}+"` s.update(match.index, match.index + full.length, replacementString) } @@ -207,7 +208,7 @@ export function assetPlugin(config: ResolvedConfig): Plugin { return { code: `export default ${JSON.stringify( - url.startsWith('data:') ? url : encodeURI(url), + url.startsWith('data:') ? url : encodeURIPath(url), )}`, // Force rollup to keep this module from being shared between other entry points if it's an entrypoint. // If the resulting chunk is empty, it will be removed in generateBundle. diff --git a/packages/vite/src/node/plugins/css.ts b/packages/vite/src/node/plugins/css.ts index 3375fa7415c439..484630645060f5 100644 --- a/packages/vite/src/node/plugins/css.ts +++ b/packages/vite/src/node/plugins/css.ts @@ -49,6 +49,7 @@ import { combineSourcemaps, createSerialPromiseQueue, emptyCssComments, + encodeURIPath, generateCodeFrame, getHash, getPackageManagerCommand, @@ -593,7 +594,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { chunkCSS = chunkCSS.replace(assetUrlRE, (_, fileHash, postfix = '') => { const filename = this.getFileName(fileHash) + postfix chunk.viteMetadata!.importedAssets.add(cleanUrl(filename)) - return encodeURI( + return encodeURIPath( toOutputFilePathInCss( filename, 'asset', @@ -612,7 +613,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { ) chunkCSS = chunkCSS.replace(publicAssetUrlRE, (_, hash) => { const publicUrl = publicAssetUrlMap.get(hash)!.slice(1) - return encodeURI( + return encodeURIPath( toOutputFilePathInCss( publicUrl, 'public', @@ -715,7 +716,7 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin { ) const replacementString = typeof replacement === 'string' - ? JSON.stringify(encodeURI(replacement)).slice(1, -1) + ? JSON.stringify(encodeURIPath(replacement)).slice(1, -1) : `"+${replacement.runtime}+"` s.update(start, end, replacementString) } diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts index f9f47b570cc209..19cc7b4c6cade2 100644 --- a/packages/vite/src/node/plugins/html.ts +++ b/packages/vite/src/node/plugins/html.ts @@ -13,12 +13,13 @@ import { stripLiteral } from 'strip-literal' import type { Plugin } from '../plugin' import type { ViteDevServer } from '../server' import { + encodeURIPath, generateCodeFrame, getHash, isDataUrl, isExternalUrl, normalizePath, - partialEncodeURI, + partialEncodeURIPath, processSrcSet, removeLeadingSlash, urlCanParse, @@ -439,7 +440,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { overwriteAttrValue( s, sourceCodeLocation!, - partialEncodeURI(toOutputPublicFilePath(url)), + partialEncodeURIPath(toOutputPublicFilePath(url)), ) } @@ -498,7 +499,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { if (!isExcludedUrl(decodedUrl)) { const result = await processAssetUrl(url) return result !== decodedUrl - ? encodeURI(result) + ? encodeURIPath(result) : url } return url @@ -519,7 +520,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { overwriteAttrValue( s, getAttrSourceCodeLocation(node, attrKey), - partialEncodeURI(toOutputPublicFilePath(url)), + partialEncodeURIPath(toOutputPublicFilePath(url)), ) } else if (!isExcludedUrl(url)) { if ( @@ -563,7 +564,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { overwriteAttrValue( s, getAttrSourceCodeLocation(node, attrKey), - partialEncodeURI(processedUrl), + partialEncodeURIPath(processedUrl), ) } })(), @@ -636,12 +637,16 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { // emit asset for (const { start, end, url } of scriptUrls) { if (checkPublicFile(url, config)) { - s.update(start, end, partialEncodeURI(toOutputPublicFilePath(url))) + s.update( + start, + end, + partialEncodeURIPath(toOutputPublicFilePath(url)), + ) } else if (!isExcludedUrl(url)) { s.update( start, end, - partialEncodeURI(await urlToBuiltUrl(url, id, config, this)), + partialEncodeURIPath(await urlToBuiltUrl(url, id, config, this)), ) } } @@ -904,7 +909,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { if (chunk) { chunk.viteMetadata!.importedAssets.add(cleanUrl(file)) } - return encodeURI(toOutputAssetFilePath(file)) + postfix + return encodeURIPath(toOutputAssetFilePath(file)) + postfix }) result = result.replace(publicAssetUrlRE, (_, fileHash) => { @@ -912,7 +917,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { getPublicAssetFilename(fileHash, config)!, ) - return encodeURI( + return encodeURIPath( urlCanParse(publicAssetPath) ? publicAssetPath : normalizePath(publicAssetPath), diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index db8256ec3d9bbb..4f69d34401699b 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -40,7 +40,7 @@ import { joinUrlSegments, moduleListContains, normalizePath, - partialEncodeURI, + partialEncodeURIPath, prettifyUrl, removeImportQuery, removeTimestampQuery, @@ -594,7 +594,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { rewriteDone = true } if (!rewriteDone) { - const rewrittenUrl = JSON.stringify(partialEncodeURI(url)) + const rewrittenUrl = JSON.stringify(partialEncodeURIPath(url)) const s = isDynamicImport ? start : start - 1 const e = isDynamicImport ? end : end + 1 str().overwrite(s, e, rewrittenUrl, { diff --git a/packages/vite/src/node/plugins/worker.ts b/packages/vite/src/node/plugins/worker.ts index 0a200cdb71007d..4094b581a52b63 100644 --- a/packages/vite/src/node/plugins/worker.ts +++ b/packages/vite/src/node/plugins/worker.ts @@ -5,7 +5,13 @@ import type { ResolvedConfig } from '../config' import type { Plugin } from '../plugin' import type { ViteDevServer } from '../server' import { ENV_ENTRY, ENV_PUBLIC_PATH } from '../constants' -import { getHash, injectQuery, prettifyUrl, urlRE } from '../utils' +import { + encodeURIPath, + getHash, + injectQuery, + prettifyUrl, + urlRE, +} from '../utils' import { createToImportMetaURLBasedRelativeRuntime, onRollupWarning, @@ -411,7 +417,7 @@ export function webWorkerPlugin(config: ResolvedConfig): Plugin { ) const replacementString = typeof replacement === 'string' - ? JSON.stringify(encodeURI(replacement)).slice(1, -1) + ? JSON.stringify(encodeURIPath(replacement)).slice(1, -1) : `"+${replacement.runtime}+"` s.update(match.index, match.index + full.length, replacementString) } diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index da754f215dac61..0352cd19e969e2 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -1418,9 +1418,20 @@ export function displayTime(time: number): string { } /** - * Like `encodeURI`, but only replacing `%` as `%25`. This is useful for environments + * Encodes the URI path portion (ignores part after ? or #) + */ +export function encodeURIPath(uri: string): string { + const filePath = cleanUrl(uri) + const postfix = filePath !== uri ? uri.slice(filePath.length) : '' + return encodeURI(filePath) + postfix +} + +/** + * Like `encodeURIPath`, but only replacing `%` as `%25`. This is useful for environments * that can handle un-encoded URIs, where `%` is the only ambiguous character. */ -export function partialEncodeURI(uri: string): string { - return uri.replaceAll('%', '%25') +export function partialEncodeURIPath(uri: string): string { + const filePath = cleanUrl(uri) + const postfix = filePath !== uri ? uri.slice(filePath.length) : '' + return filePath.replaceAll('%', '%25') + postfix }