diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index c315d5b29dcba0..85e12b89d722fa 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -48,6 +48,11 @@ import { getImportMapFilename } from './html' // referenceId is base64url but replaces - with $ export const assetUrlRE: RegExp = /__VITE_ASSET__([\w$]+)__/g +const encodedHashPlaceholderRE = /!~%7B([\w$]{1,17})%7D~/g +function unescapeHashPlaceholders(uri: string): string { + return uri.replace(encodedHashPlaceholderRE, '!~{$1}~') +} + interface FileUrlMetadata { asFileUrl: boolean } @@ -329,7 +334,9 @@ export function assetPlugin(config: ResolvedConfig): Plugin { toRelativeRuntime, ) return typeof replacement === 'string' - ? JSON.stringify(encodeURIPath(replacement)) + ? JSON.stringify( + unescapeHashPlaceholders(encodeURIPath(replacement)), + ) : replacement.runtime }, diff --git a/playground/assets/__tests__/assets.spec.ts b/playground/assets/__tests__/assets.spec.ts index 46e540d5c7a610..222ae0ba88cafe 100644 --- a/playground/assets/__tests__/assets.spec.ts +++ b/playground/assets/__tests__/assets.spec.ts @@ -218,6 +218,20 @@ describe('asset imports from js', () => { }) }) +describe.runIf(isBuild)('file URL emitted by an external plugin', () => { + test('replaces the preliminary hash', async () => { + expect(await page.textContent('.emitted-worker-url')).toMatch( + /^\/foo\/bar\/assets\/emitted-worker-[-\w]{8}\.js$/, + ) + }) + + test('loads the emitted chunk', async () => { + await expect + .poll(() => page.textContent('.emitted-worker-result')) + .toBe('worker loaded') + }) +}) + describe('css url() references', () => { test('fonts', async () => { expect( diff --git a/playground/assets/__tests__/encoded-base/assets-encoded-base.spec.ts b/playground/assets/__tests__/encoded-base/assets-encoded-base.spec.ts index 900fb34df2c9a8..c3adcf72cb4af6 100644 --- a/playground/assets/__tests__/encoded-base/assets-encoded-base.spec.ts +++ b/playground/assets/__tests__/encoded-base/assets-encoded-base.spec.ts @@ -73,6 +73,12 @@ describe('asset imports from js', () => { }) }) +test.runIf(isBuild)('loads the emitted worker', async () => { + await expect + .poll(() => page.textContent('.emitted-worker-result')) + .toBe('worker loaded') +}) + describe('css url() references', () => { test('fonts', async () => { expect( diff --git a/playground/assets/__tests__/relative-base/assets-relative-base.spec.ts b/playground/assets/__tests__/relative-base/assets-relative-base.spec.ts index 69b2b324119891..d786b3ff2b6288 100644 --- a/playground/assets/__tests__/relative-base/assets-relative-base.spec.ts +++ b/playground/assets/__tests__/relative-base/assets-relative-base.spec.ts @@ -77,6 +77,12 @@ describe('asset imports from js', () => { }) }) +test.runIf(isBuild)('loads the emitted worker', async () => { + await expect + .poll(() => page.textContent('.emitted-worker-result')) + .toBe('worker loaded') +}) + describe('css url() references', () => { test('fonts', async () => { expect( diff --git a/playground/assets/__tests__/runtime-base/assets-runtime-base.spec.ts b/playground/assets/__tests__/runtime-base/assets-runtime-base.spec.ts index 2b89521b8cabc5..39933d1836b766 100644 --- a/playground/assets/__tests__/runtime-base/assets-runtime-base.spec.ts +++ b/playground/assets/__tests__/runtime-base/assets-runtime-base.spec.ts @@ -70,6 +70,12 @@ describe('asset imports from js', () => { }) }) +test.runIf(isBuild)('loads the emitted worker', async () => { + await expect + .poll(() => page.textContent('.emitted-worker-result')) + .toBe('worker loaded') +}) + describe('css url() references', () => { test('fonts', async () => { expect( diff --git a/playground/assets/__tests__/url-base/assets-url-base.spec.ts b/playground/assets/__tests__/url-base/assets-url-base.spec.ts index 0e7b044d7e9be9..fd106883c57ed8 100644 --- a/playground/assets/__tests__/url-base/assets-url-base.spec.ts +++ b/playground/assets/__tests__/url-base/assets-url-base.spec.ts @@ -75,6 +75,12 @@ describe('asset imports from js', () => { }) }) +test.runIf(isBuild)('loads the emitted worker', async () => { + await expect + .poll(() => page.textContent('.emitted-worker-result')) + .toBe('worker loaded') +}) + describe('css url() references', () => { test('fonts', async () => { expect( diff --git a/playground/assets/asset/emitted-worker.js b/playground/assets/asset/emitted-worker.js new file mode 100644 index 00000000000000..ea9f1d6518605c --- /dev/null +++ b/playground/assets/asset/emitted-worker.js @@ -0,0 +1 @@ +self.postMessage('worker loaded') diff --git a/playground/assets/asset/main.js b/playground/assets/asset/main.js index e889874ff000d2..18bf777df83987 100644 --- a/playground/assets/asset/main.js +++ b/playground/assets/asset/main.js @@ -1,4 +1,14 @@ +import emittedWorkerUrl from 'virtual:emitted-worker-url' + function text(el, text) { document.querySelector(el).textContent = text } text('.relative-js', 'hello') + +if (emittedWorkerUrl) { + text('.emitted-worker-url', emittedWorkerUrl) + const worker = new Worker(emittedWorkerUrl, { type: 'module' }) + worker.addEventListener('message', ({ data }) => { + text('.emitted-worker-result', data) + }) +} diff --git a/playground/assets/index.html b/playground/assets/index.html index 6ba5233498bcd4..d9b395be1c12a1 100644 --- a/playground/assets/index.html +++ b/playground/assets/index.html @@ -54,6 +54,10 @@

Asset Imports from JS

+

File URL emitted by an external plugin

+pending +pending +

CSS url references

Font should be loaded (all text should be italic)
diff --git a/playground/assets/vite.config-runtime-base.js b/playground/assets/vite.config-runtime-base.js index 8fbed84bcdb860..c505feec8ac95a 100644 --- a/playground/assets/vite.config-runtime-base.js +++ b/playground/assets/vite.config-runtime-base.js @@ -24,6 +24,7 @@ export default defineConfig({ }, }, plugins: [ + ...baseConfig.plugins, { name: 'dynamic-base-assets-globals', transformIndexHtml(_, ctx) { diff --git a/playground/assets/vite.config.js b/playground/assets/vite.config.js index 9bae9842ecab90..82bc7fc6b2ee91 100644 --- a/playground/assets/vite.config.js +++ b/playground/assets/vite.config.js @@ -16,6 +16,26 @@ export default defineConfig({ }, }, assetsInclude: ['**/*.unknown', './nested/*.custom'], + plugins: [ + { + name: 'emitted-worker-url', + resolveId(id) { + if (id === 'virtual:emitted-worker-url') { + return '\0virtual:emitted-worker-url' + } + }, + load(id) { + if (id !== '\0virtual:emitted-worker-url') return + if (this.environment.mode !== 'build') return 'export default undefined' + + const referenceId = this.emitFile({ + type: 'chunk', + id: path.resolve(import.meta.dirname, 'asset/emitted-worker.js'), + }) + return `export default import.meta.ROLLUP_FILE_URL_${referenceId}` + }, + }, + ], build: { outDir: 'dist/foo', assetsInlineLimit: 8000, // 8 kB