From 5acda5e10a70908e82093b5cd302d103a346e693 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arnaud=20Barr=C3=A9?= Date: Thu, 19 Oct 2023 09:36:29 +0200 Subject: [PATCH] feat(build)!: inline SVGs (#14643) --- packages/vite/src/node/plugins/asset.ts | 38 +++++++++++++++++-- playground/assets/__tests__/assets.spec.ts | 7 +++- playground/legacy/vite.config.js | 1 + playground/worker/vite.config-es.js | 1 + playground/worker/vite.config-iife.js | 1 + .../worker/vite.config-relative-base-iife.js | 1 + .../worker/vite.config-relative-base.js | 1 + playground/worker/worker-sourcemap-config.js | 1 + 8 files changed, 46 insertions(+), 5 deletions(-) diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index eb4f65341a69a8..3a850407569aff 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -371,7 +371,8 @@ async function fileToBuiltUrl( let url: string if ( config.build.lib || - (!file.endsWith('.svg') && + // Don't inline SVG with fragments, as they are meant to be reused + (!(file.endsWith('.svg') && id.includes('#')) && !file.endsWith('.html') && content.length < Number(config.build.assetsInlineLimit) && !isGitLfsPlaceholder(content)) @@ -382,9 +383,13 @@ async function fileToBuiltUrl( ) } - const mimeType = mrmime.lookup(file) ?? 'application/octet-stream' - // base64 inlined as a string - url = `data:${mimeType};base64,${content.toString('base64')}` + if (file.endsWith('.svg')) { + url = svgToDataURL(content) + } else { + const mimeType = mrmime.lookup(file) ?? 'application/octet-stream' + // base64 inlined as a string + url = `data:${mimeType};base64,${content.toString('base64')}` + } } else { // emit as asset const { search, hash } = parseUrl(id) @@ -428,3 +433,28 @@ export async function urlToBuiltUrl( true, ) } + +// Inspired by https://github.com/iconify/iconify/blob/main/packages/utils/src/svg/url.ts +function svgToDataURL(content: Buffer): string { + const stringContent = content.toString() + // If the SVG contains some text, any transformation is unsafe, and given that double quotes would then + // need to be escaped, the gain to use a data URI would be ridiculous if not negative + if (stringContent.includes('', '%3e') + // Spaces are not valid in srcset it has some use cases + // it can make the uncompressed URI slightly higher than base64, but will compress way better + // https://github.com/vitejs/vite/pull/14643#issuecomment-1766288673 + .replaceAll(/\s+/g, '%20') + ) + } +} diff --git a/playground/assets/__tests__/assets.spec.ts b/playground/assets/__tests__/assets.spec.ts index f2877146ea691b..c44127ed73dd70 100644 --- a/playground/assets/__tests__/assets.spec.ts +++ b/playground/assets/__tests__/assets.spec.ts @@ -293,7 +293,12 @@ describe('svg fragments', () => { test('from js import', async () => { const img = await page.$('.svg-frag-import') - expect(await img.getAttribute('src')).toMatch(/svg#icon-heart-view$/) + expect(await img.getAttribute('src')).toMatch( + isBuild + ? // Assert trimmed (data URI starts with < and ends with >) + /^data:image\/svg\+xml,%3c.*%3e#icon-heart-view$/ + : /svg#icon-heart-view$/, + ) }) }) diff --git a/playground/legacy/vite.config.js b/playground/legacy/vite.config.js index 4f319e250eb729..d07d295a0a27d8 100644 --- a/playground/legacy/vite.config.js +++ b/playground/legacy/vite.config.js @@ -16,6 +16,7 @@ export default defineConfig({ cssCodeSplit: false, manifest: true, sourcemap: true, + assetsInlineLimit: 100, // keep SVG as assets URL rollupOptions: { input: { index: path.resolve(__dirname, 'index.html'), diff --git a/playground/worker/vite.config-es.js b/playground/worker/vite.config-es.js index bd82434b99ab89..0049c5357fa0af 100644 --- a/playground/worker/vite.config-es.js +++ b/playground/worker/vite.config-es.js @@ -21,6 +21,7 @@ export default defineConfig({ }, build: { outDir: 'dist/es', + assetsInlineLimit: 100, // keep SVG as assets URL rollupOptions: { output: { assetFileNames: 'assets/[name].[ext]', diff --git a/playground/worker/vite.config-iife.js b/playground/worker/vite.config-iife.js index c98f7433383ef4..98757b8c00d2fd 100644 --- a/playground/worker/vite.config-iife.js +++ b/playground/worker/vite.config-iife.js @@ -38,6 +38,7 @@ export default defineConfig({ }, build: { outDir: 'dist/iife', + assetsInlineLimit: 100, // keep SVG as assets URL manifest: true, rollupOptions: { output: { diff --git a/playground/worker/vite.config-relative-base-iife.js b/playground/worker/vite.config-relative-base-iife.js index 77712979781705..6b3c7c6895f46a 100644 --- a/playground/worker/vite.config-relative-base-iife.js +++ b/playground/worker/vite.config-relative-base-iife.js @@ -21,6 +21,7 @@ export default defineConfig({ }, build: { outDir: 'dist/relative-base-iife', + assetsInlineLimit: 100, // keep SVG as assets URL rollupOptions: { output: { assetFileNames: 'other-assets/[name]-[hash].[ext]', diff --git a/playground/worker/vite.config-relative-base.js b/playground/worker/vite.config-relative-base.js index dd5af51bd8e263..a09aafa3b841aa 100644 --- a/playground/worker/vite.config-relative-base.js +++ b/playground/worker/vite.config-relative-base.js @@ -21,6 +21,7 @@ export default defineConfig({ }, build: { outDir: 'dist/relative-base', + assetsInlineLimit: 100, // keep SVG as assets URL rollupOptions: { output: { assetFileNames: 'other-assets/[name]-[hash].[ext]', diff --git a/playground/worker/worker-sourcemap-config.js b/playground/worker/worker-sourcemap-config.js index 89178e952ef03b..4ea0162f705119 100644 --- a/playground/worker/worker-sourcemap-config.js +++ b/playground/worker/worker-sourcemap-config.js @@ -35,6 +35,7 @@ export default (sourcemap) => { }, build: { outDir: `dist/iife-${typeName}/`, + assetsInlineLimit: 100, // keep SVG as assets URL sourcemap: sourcemap, rollupOptions: { output: {