diff --git a/packages/vite/src/node/plugins/assetImportMetaUrl.ts b/packages/vite/src/node/plugins/assetImportMetaUrl.ts index d16b1ad7540b64..d0266fed75b1d0 100644 --- a/packages/vite/src/node/plugins/assetImportMetaUrl.ts +++ b/packages/vite/src/node/plugins/assetImportMetaUrl.ts @@ -56,11 +56,14 @@ export function assetImportMetaUrlPlugin(config: ResolvedConfig): Plugin { // potential dynamic template string if (rawUrl[0] === '`' && rawUrl.includes('${')) { - let [pureUrl, queryString = ''] = rawUrl.split('?') - if (queryString) { - pureUrl += '`' - queryString = '?' + queryString.slice(0, -1) - } + const queryDelimiterIndex = getQueryDelimiterIndex(rawUrl) + const hasQueryDelimiter = queryDelimiterIndex !== -1 + const pureUrl = hasQueryDelimiter + ? rawUrl.slice(0, queryDelimiterIndex) + '`' + : rawUrl + const queryString = hasQueryDelimiter + ? rawUrl.slice(queryDelimiterIndex, -1) + : '' const ast = this.parse(pureUrl) const templateLiteral = (ast as any).body[0].expression if (templateLiteral.expressions.length) { @@ -167,3 +170,17 @@ function buildGlobPattern(ast: any) { } return pattern } + +function getQueryDelimiterIndex(rawUrl: string): number { + let bracketsStack = 0 + for (let i = 0; i < rawUrl.length; i++) { + if (rawUrl[i] === '{') { + bracketsStack++ + } else if (rawUrl[i] === '}') { + bracketsStack-- + } else if (rawUrl[i] === '?' && bracketsStack === 0) { + return i + } + } + return -1 +} diff --git a/playground/assets/__tests__/assets.spec.ts b/playground/assets/__tests__/assets.spec.ts index 29d56820c6c4bf..114440059b787d 100644 --- a/playground/assets/__tests__/assets.spec.ts +++ b/playground/assets/__tests__/assets.spec.ts @@ -335,6 +335,17 @@ test('new URL(`./${dynamic}?abc`, import.meta.url)', async () => { ) }) +test('new URL(`./${1 === 0 ? static : dynamic}?abc`, import.meta.url)', async () => { + expect(await page.textContent('.dynamic-import-meta-url-1-ternary')).toMatch( + isBuild ? 'data:image/png;base64' : '/foo/nested/icon.png?abc', + ) + expect(await page.textContent('.dynamic-import-meta-url-2-ternary')).toMatch( + isBuild + ? /\/foo\/assets\/asset-\w{8}\.png\?abc/ + : '/foo/nested/asset.png?abc', + ) +}) + test('new URL(`non-existent`, import.meta.url)', async () => { expect(await page.textContent('.non-existent-import-meta-url')).toMatch( new URL('non-existent', page.url()).pathname, diff --git a/playground/assets/index.html b/playground/assets/index.html index b9e857398b6c35..463d5a6331a2f0 100644 --- a/playground/assets/index.html +++ b/playground/assets/index.html @@ -231,6 +231,16 @@

new URL(`./${dynamic}?abc`, import.meta.url)

+

new URL(`./${1 === 0 ? static : dynamic}?abc`, import.meta.url)

+

+ + +

+

+ + +

+

new URL(`non-existent`, import.meta.url)

@@ -453,6 +463,17 @@

assets in noscript

testDynamicImportMetaUrlWithQuery('icon', 1) testDynamicImportMetaUrlWithQuery('asset', 2) + function testDynamicImportMetaUrlWithTernaryOperator(name, i) { + // prettier-ignore + const metaUrl = new URL(`./nested/${1 === 0 ? 'failed' : name}.png?abc`, import.meta.url,) + text(`.dynamic-import-meta-url-${i}-ternary`, metaUrl) + document.querySelector(`.dynamic-import-meta-url-img-${i}-ternary`).src = + metaUrl + } + + testDynamicImportMetaUrlWithTernaryOperator('icon', 1) + testDynamicImportMetaUrlWithTernaryOperator('asset', 2) + { const name = 'test' const js = new URL(`./nested/${name}.js`, import.meta.url).href