Skip to content
Merged
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
36 changes: 33 additions & 3 deletions packages/vite/src/node/__tests__/build.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
} from '../build'
import type { Logger } from '../logger'
import { createLogger } from '../logger'
import { injectQuery } from '../utils'

const dirname = import.meta.dirname

Expand Down Expand Up @@ -112,6 +113,26 @@ describe('build', () => {
assertOutputHashContentChange(result[0], result[1])
})

test('renderBuiltUrl receives asset postfixes in JS and CSS', async () => {
const result = await buildProjectWithRenderBuiltUrl(
(filename) => injectQuery(filename, 'dpl=id'),
true,
)
const entry = result.output.find(
(output): output is OutputChunk =>
output.type === 'chunk' && output.isEntry,
)
const css = result.output.find(
(output): output is OutputAsset =>
output.type === 'asset' && output.fileName.endsWith('.css'),
)

expect(entry?.code).toContain('?dpl=id&marker=value')
expect(entry?.code).toContain('?dpl=id&marker=other')
expect(css?.source.toString()).toContain('?dpl=id&marker=value')
expect(css?.source.toString()).toContain('?dpl=id&marker=other')
})

test('top-level input is used as the default build entry', async () => {
const result = (await build({
root: resolve(dirname, 'packages/build-project'),
Expand Down Expand Up @@ -1564,6 +1585,7 @@ test('copies public directory after building same environment with write false f

async function buildProjectWithRenderBuiltUrl(
renderBuiltUrl: (filename: string) => string,
includePostfixes = false,
) {
return (await build({
root: resolve(dirname, 'packages/build-project'),
Expand All @@ -1579,21 +1601,29 @@ async function buildProjectWithRenderBuiltUrl(
{
name: 'test',
resolveId(id) {
if (id === 'entry.js' || id === 'subentry.js') {
if (id === 'entry.js' || id === 'subentry.js' || id === 'style.css') {
return '\0' + id
}
},
load(id) {
if (id === '\0entry.js') {
return `
import assetUrl from '/asset.txt?url'
console.log(assetUrl)
import assetUrl from '/asset.txt?url${includePostfixes ? '&marker=value' : ''}'
${includePostfixes ? `import otherAssetUrl from '/asset.txt?url&marker=other'` : ''}
${includePostfixes ? `import 'style.css'` : ''}
console.log(assetUrl${includePostfixes ? `, otherAssetUrl` : ''})
window.addEventListener('click', () => { import('subentry.js') })
`
}
if (id === '\0subentry.js') {
return `export default 'subentry'`
}
if (id === '\0style.css') {
return `
.asset-a { background: url('/asset.txt?marker=value') }
.asset-b { background: url('/asset.txt?marker=other') }
`
}
},
},
],
Expand Down
58 changes: 44 additions & 14 deletions packages/vite/src/node/plugins/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ import {
} from '../utils'
import { getImportMapFilename } from './html'

// referenceId is base64url but replaces - with $
export const assetUrlRE: RegExp = /__VITE_ASSET__([\w$]+)__/g
// referenceId is a 22-character base64url string but replaces - with $
export const assetUrlRE: RegExp =
/__VITE_ASSET__([\w$]{22})(?:_([a-f\d]{24}))?__/g

const encodedHashPlaceholderRE = /!~%7B([\w$]{1,17})%7D~/g
function unescapeHashPlaceholders(uri: string): string {
Expand All @@ -55,6 +56,7 @@ function unescapeHashPlaceholders(uri: string): string {

interface FileUrlMetadata {
asFileUrl: boolean
postfix: string
}

const fileUrlMetadata = new WeakMap<Environment, Map<string, FileUrlMetadata>>()
Expand Down Expand Up @@ -140,12 +142,12 @@ export function renderAssetUrlInJS(
assetUrlRE.lastIndex = 0
while ((match = assetUrlRE.exec(code))) {
s ||= new MagicString(code)
const [full, referenceId] = match
const [full, referenceId, urlId] = match
const file = pluginContext.getFileName(referenceId)
chunk.viteMetadata!.importedAssets.add(cleanUrl(file))
const replacement = toOutputFilePathInJS(
environment,
file,
file + getAssetUrlPostfix(environment, urlId),
'asset',
chunk.fileName,
'js',
Expand Down Expand Up @@ -285,7 +287,11 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
}

return {
code: `export default ${formatBuiltAsset(resolved, 'js')}`,
code: `export default ${formatBuiltAsset(
resolved,
'js',
addFileUrlMetadataForAsset(this.environment, resolved, 'js'),
)}`,
// 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.
moduleSideEffects:
Expand Down Expand Up @@ -327,7 +333,7 @@ export function assetPlugin(config: ResolvedConfig): Plugin {
}
const replacement = toOutputFilePathInJS(
environment,
fileName,
fileName + (metadata?.postfix ?? ''),
'asset',
chunkId,
'js',
Expand Down Expand Up @@ -555,13 +561,30 @@ async function fileToBuiltUrl(
skipPublicCheck,
forceInline,
)
const urlId =
resolved.type === 'reference' && format === 'js' && asFileUrl
? addFileUrlMetadata(pluginContext.environment, { asFileUrl })
: undefined
const urlId = addFileUrlMetadataForAsset(
pluginContext.environment,
resolved,
format,
asFileUrl,
)
return formatBuiltAsset(resolved, format, urlId)
}

function addFileUrlMetadataForAsset(
environment: Environment,
resolved: FileToBuiltUrlResult,
format: AssetUrlFormat,
useFileUrl = false,
): string | undefined {
if (resolved.type !== 'reference') return

const asFileUrl = format === 'js' && useFileUrl
const { postfix } = resolved
if (!asFileUrl && !postfix) return

return addFileUrlMetadata(environment, { asFileUrl, postfix })
}

function addFileUrlMetadata(
environment: Environment,
metadata: FileUrlMetadata,
Expand All @@ -580,6 +603,15 @@ function addFileUrlMetadata(
return urlId
}

export function getAssetUrlPostfix(
environment: Environment,
urlId: string | undefined,
): string {
return urlId
? (fileUrlMetadata.get(environment)?.get(urlId)?.postfix ?? '')
: ''
}

/** Format a resolved asset as either a JS expression or a plain-text string. */
function formatBuiltAsset(
resolved: FileToBuiltUrlResult,
Expand All @@ -591,11 +623,9 @@ function formatBuiltAsset(
const base = urlId
? `import.meta.ROLLDOWN_FILE_URL_${resolved.referenceId}_${urlId}`
: `import.meta.ROLLDOWN_FILE_URL_${resolved.referenceId}`
return resolved.postfix
? `${base} + ${JSON.stringify(resolved.postfix)}`
: base
return base
}
return `__VITE_ASSET__${resolved.referenceId}__${resolved.postfix}`
return `__VITE_ASSET__${resolved.referenceId}${urlId ? `_${urlId}` : ''}__`
}
return format === 'js'
? JSON.stringify(encodeURIPath(resolved.value))
Expand Down
7 changes: 5 additions & 2 deletions packages/vite/src/node/plugins/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import {
assetUrlRE,
cssEntriesMap,
fileToUrl,
getAssetUrlPostfix,
publicAssetUrlCache,
publicAssetUrlRE,
publicFileToBuiltUrl,
Expand Down Expand Up @@ -751,8 +752,10 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
}

// replace asset url references with resolved url.
chunkCSS = chunkCSS.replace(assetUrlRE, (_, fileHash) => {
const filename = this.getFileName(fileHash)
chunkCSS = chunkCSS.replace(assetUrlRE, (_, fileHash, urlId) => {
const filename =
this.getFileName(fileHash) +
getAssetUrlPostfix(this.environment, urlId)
chunk.viteMetadata!.importedAssets.add(cleanUrl(filename))
return encodeURIPath(
toOutputFilePathInCss(
Expand Down
8 changes: 6 additions & 2 deletions packages/vite/src/node/plugins/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
} from '../utils'
import {
assetUrlRE,
getAssetUrlPostfix,
getPublicAssetFilename,
publicAssetUrlRE,
urlToBuiltUrl,
Expand Down Expand Up @@ -1074,12 +1075,15 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
},
)
// resolve asset url references
result = result.replace(assetUrlRE, (_, fileHash) => {
result = result.replace(assetUrlRE, (_, fileHash, urlId) => {
const file = this.getFileName(fileHash)
if (chunk) {
chunk.viteMetadata!.importedAssets.add(cleanUrl(file))
}
return encodeURIPath(toOutputAssetFilePath(file))
return (
encodeURIPath(toOutputAssetFilePath(file)) +
getAssetUrlPostfix(this.environment, urlId)
)
})

result = result.replace(publicAssetUrlRE, (_, fileHash) => {
Expand Down
Loading