Skip to content

Commit 5acda5e

Browse files
authored
feat(build)!: inline SVGs (#14643)
1 parent e5ee420 commit 5acda5e

File tree

8 files changed

+46
-5
lines changed

8 files changed

+46
-5
lines changed

packages/vite/src/node/plugins/asset.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,8 @@ async function fileToBuiltUrl(
371371
let url: string
372372
if (
373373
config.build.lib ||
374-
(!file.endsWith('.svg') &&
374+
// Don't inline SVG with fragments, as they are meant to be reused
375+
(!(file.endsWith('.svg') && id.includes('#')) &&
375376
!file.endsWith('.html') &&
376377
content.length < Number(config.build.assetsInlineLimit) &&
377378
!isGitLfsPlaceholder(content))
@@ -382,9 +383,13 @@ async function fileToBuiltUrl(
382383
)
383384
}
384385

385-
const mimeType = mrmime.lookup(file) ?? 'application/octet-stream'
386-
// base64 inlined as a string
387-
url = `data:${mimeType};base64,${content.toString('base64')}`
386+
if (file.endsWith('.svg')) {
387+
url = svgToDataURL(content)
388+
} else {
389+
const mimeType = mrmime.lookup(file) ?? 'application/octet-stream'
390+
// base64 inlined as a string
391+
url = `data:${mimeType};base64,${content.toString('base64')}`
392+
}
388393
} else {
389394
// emit as asset
390395
const { search, hash } = parseUrl(id)
@@ -428,3 +433,28 @@ export async function urlToBuiltUrl(
428433
true,
429434
)
430435
}
436+
437+
// Inspired by https://github.com/iconify/iconify/blob/main/packages/utils/src/svg/url.ts
438+
function svgToDataURL(content: Buffer): string {
439+
const stringContent = content.toString()
440+
// If the SVG contains some text, any transformation is unsafe, and given that double quotes would then
441+
// need to be escaped, the gain to use a data URI would be ridiculous if not negative
442+
if (stringContent.includes('<text')) {
443+
return `data:image/svg+xml;base64,${content.toString('base64')}`
444+
} else {
445+
return (
446+
'data:image/svg+xml,' +
447+
stringContent
448+
.trim()
449+
.replaceAll('"', "'")
450+
.replaceAll('%', '%25')
451+
.replaceAll('#', '%23')
452+
.replaceAll('<', '%3c')
453+
.replaceAll('>', '%3e')
454+
// Spaces are not valid in srcset it has some use cases
455+
// it can make the uncompressed URI slightly higher than base64, but will compress way better
456+
// https://github.com/vitejs/vite/pull/14643#issuecomment-1766288673
457+
.replaceAll(/\s+/g, '%20')
458+
)
459+
}
460+
}

playground/assets/__tests__/assets.spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,12 @@ describe('svg fragments', () => {
293293

294294
test('from js import', async () => {
295295
const img = await page.$('.svg-frag-import')
296-
expect(await img.getAttribute('src')).toMatch(/svg#icon-heart-view$/)
296+
expect(await img.getAttribute('src')).toMatch(
297+
isBuild
298+
? // Assert trimmed (data URI starts with < and ends with >)
299+
/^data:image\/svg\+xml,%3c.*%3e#icon-heart-view$/
300+
: /svg#icon-heart-view$/,
301+
)
297302
})
298303
})
299304

playground/legacy/vite.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export default defineConfig({
1616
cssCodeSplit: false,
1717
manifest: true,
1818
sourcemap: true,
19+
assetsInlineLimit: 100, // keep SVG as assets URL
1920
rollupOptions: {
2021
input: {
2122
index: path.resolve(__dirname, 'index.html'),

playground/worker/vite.config-es.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default defineConfig({
2121
},
2222
build: {
2323
outDir: 'dist/es',
24+
assetsInlineLimit: 100, // keep SVG as assets URL
2425
rollupOptions: {
2526
output: {
2627
assetFileNames: 'assets/[name].[ext]',

playground/worker/vite.config-iife.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export default defineConfig({
3838
},
3939
build: {
4040
outDir: 'dist/iife',
41+
assetsInlineLimit: 100, // keep SVG as assets URL
4142
manifest: true,
4243
rollupOptions: {
4344
output: {

playground/worker/vite.config-relative-base-iife.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default defineConfig({
2121
},
2222
build: {
2323
outDir: 'dist/relative-base-iife',
24+
assetsInlineLimit: 100, // keep SVG as assets URL
2425
rollupOptions: {
2526
output: {
2627
assetFileNames: 'other-assets/[name]-[hash].[ext]',

playground/worker/vite.config-relative-base.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default defineConfig({
2121
},
2222
build: {
2323
outDir: 'dist/relative-base',
24+
assetsInlineLimit: 100, // keep SVG as assets URL
2425
rollupOptions: {
2526
output: {
2627
assetFileNames: 'other-assets/[name]-[hash].[ext]',

playground/worker/worker-sourcemap-config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export default (sourcemap) => {
3535
},
3636
build: {
3737
outDir: `dist/iife-${typeName}/`,
38+
assetsInlineLimit: 100, // keep SVG as assets URL
3839
sourcemap: sourcemap,
3940
rollupOptions: {
4041
output: {

0 commit comments

Comments
 (0)