diff --git a/packages/vite/src/node/plugins/html.ts b/packages/vite/src/node/plugins/html.ts index d3488d5bb092c9..2f3e005a782423 100644 --- a/packages/vite/src/node/plugins/html.ts +++ b/packages/vite/src/node/plugins/html.ts @@ -168,6 +168,9 @@ const noInlineLinkRels = new Set([ 'apple-touch-icon', 'apple-touch-startup-image', 'manifest', + 'modulepreload', + 'preload', + 'prefetch', ]) export const isAsyncScriptMap: WeakMap< @@ -622,6 +625,16 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { config.html?.additionalAssetSources, ) for (const attr of assetAttributes) { + // If the node is a link, check if it can be inlined. If not, set `shouldInline` + // to `false` to force no inline. If `undefined`, it leaves to the default heuristics. + const isNoInlineLink = + node.nodeName === 'link' && + attr.attributes.rel && + parseRelAttr(attr.attributes.rel).some((v) => + noInlineLinkRels.has(v), + ) + const shouldInline = isNoInlineLink ? false : undefined + if (attr.type === 'remove') { s.remove(attr.location.startOffset, attr.location.endOffset) continue @@ -636,7 +649,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { decodedUrl !== undefined && !isExcludedUrl(decodedUrl) ) { - const result = await processAssetUrl(url) + const result = await processAssetUrl(url, shouldInline) return result !== decodedUrl ? encodeURIPath(result) : url @@ -675,15 +688,6 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin { }) js += importExpression } else { - // If the node is a link, check if it can be inlined. If not, set `shouldInline` - // to `false` to force no inline. If `undefined`, it leaves to the default heuristics. - const isNoInlineLink = - node.nodeName === 'link' && - attr.attributes.rel && - parseRelAttr(attr.attributes.rel).some((v) => - noInlineLinkRels.has(v), - ) - const shouldInline = isNoInlineLink ? false : undefined assetUrlsPromises.push( (async () => { const processedUrl = await processAssetUrl( diff --git a/playground/assets/__tests__/assets.spec.ts b/playground/assets/__tests__/assets.spec.ts index 46e540d5c7a610..f568d4c3eeb5dd 100644 --- a/playground/assets/__tests__/assets.spec.ts +++ b/playground/assets/__tests__/assets.spec.ts @@ -314,6 +314,36 @@ describe('css url() references', () => { expect(await getBg('.css-url-quotes-base64-inline')).toMatch(match) }) + test('no base64 inline for modulepreload links', async () => { + const el = await page.$(`link[rel="modulepreload"]`) + const href = await el.getAttribute('href') + expect(href).toMatch( + isBundled + ? /\/foo\/bar\/assets\/preload-module-[-\w]{8}\.js/ + : 'preload-module.js', + ) + }) + + test('no base64 inline for preload and prefetch links', async () => { + const preloadAssetMatch = isBundled + ? /\/foo\/bar\/assets\/preload-asset-[-\w]{8}\.png/ + : '/foo/bar/nested/preload-asset.png' + + const preloadEl = await page.$('link.preload-href') + expect(await preloadEl.getAttribute('href')).toMatch(preloadAssetMatch) + + const prefetchEl = await page.$('link.prefetch-href') + expect(await prefetchEl.getAttribute('href')).toMatch(preloadAssetMatch) + + // `imagesrcset` goes through the srcset branch, which has to honour the + // same no-inline decision as `href` + const imageSrcSetEl = await page.$('link.preload-imagesrcset') + const imageSrcSet = await imageSrcSetEl.getAttribute('imagesrcset') + imageSrcSet.split(', ').forEach((s) => { + expect(s).toMatch(preloadAssetMatch) + }) + }) + test('no base64 inline for icon and manifest links', async () => { const iconEl = await page.$(`link.ico`) const href = await iconEl.getAttribute('href') diff --git a/playground/assets/index.html b/playground/assets/index.html index 6ba5233498bcd4..728c3687e5b554 100644 --- a/playground/assets/index.html +++ b/playground/assets/index.html @@ -4,6 +4,24 @@ + + + + , small enough to hit assetsInlineLimit +export const preloadedModule = 'preloaded'