feat(build)!: inline SVGs (#14643)

This commit is contained in:
Arnaud Barré 2023-10-19 09:36:29 +02:00 committed by GitHub
parent e5ee4207d9
commit 5acda5e10a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 46 additions and 5 deletions

View File

@ -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('<text')) {
return `data:image/svg+xml;base64,${content.toString('base64')}`
} else {
return (
'data:image/svg+xml,' +
stringContent
.trim()
.replaceAll('"', "'")
.replaceAll('%', '%25')
.replaceAll('#', '%23')
.replaceAll('<', '%3c')
.replaceAll('>', '%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')
)
}
}

View File

@ -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$/,
)
})
})

View File

@ -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'),

View File

@ -21,6 +21,7 @@ export default defineConfig({
},
build: {
outDir: 'dist/es',
assetsInlineLimit: 100, // keep SVG as assets URL
rollupOptions: {
output: {
assetFileNames: 'assets/[name].[ext]',

View File

@ -38,6 +38,7 @@ export default defineConfig({
},
build: {
outDir: 'dist/iife',
assetsInlineLimit: 100, // keep SVG as assets URL
manifest: true,
rollupOptions: {
output: {

View File

@ -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]',

View File

@ -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]',

View File

@ -35,6 +35,7 @@ export default (sourcemap) => {
},
build: {
outDir: `dist/iife-${typeName}/`,
assetsInlineLimit: 100, // keep SVG as assets URL
sourcemap: sourcemap,
rollupOptions: {
output: {