Skip to content

Commit e2746ef

Browse files
authored
feat: set intrinsic width and height for SVGs (#13126)
1 parent eb25a62 commit e2746ef

File tree

14 files changed

+169
-160
lines changed

14 files changed

+169
-160
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/enhanced-img': patch
3+
---
4+
5+
feat: set intrinsic width and height for SVGs

packages/enhanced-img/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"types": "types/index.d.ts",
3838
"dependencies": {
3939
"magic-string": "^0.30.5",
40+
"sharp": "^0.33.5",
4041
"svelte-parse-markup": "^0.1.5",
4142
"vite-imagetools": "^7.0.1",
4243
"zimmerframe": "^1.1.2"

packages/enhanced-img/src/preprocessor.js

Lines changed: 33 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import { existsSync } from 'node:fs';
44
import * as path from 'node:path';
55

66
import MagicString from 'magic-string';
7-
import { walk } from 'zimmerframe';
7+
import sharp from 'sharp';
88
import { parse } from 'svelte-parse-markup';
9+
import { walk } from 'zimmerframe';
910

1011
// TODO: expose this in vite-imagetools rather than duplicating it
1112
const OPTIMIZABLE = /^[^?]+\.(avif|heif|gif|jpeg|jpg|png|tiff|webp)(\?.*)?$/;
@@ -70,51 +71,51 @@ export function image(opts) {
7071
const original_url = src_attribute.raw.trim();
7172
let url = original_url;
7273

73-
const sizes = get_attr_value(node, 'sizes');
74-
const width = get_attr_value(node, 'width');
75-
url += url.includes('?') ? '&' : '?';
76-
if (sizes && 'raw' in sizes) {
77-
url += 'imgSizes=' + encodeURIComponent(sizes.raw) + '&';
78-
}
79-
if (width && 'raw' in width) {
80-
url += 'imgWidth=' + encodeURIComponent(width.raw) + '&';
74+
if (OPTIMIZABLE.test(url)) {
75+
const sizes = get_attr_value(node, 'sizes');
76+
const width = get_attr_value(node, 'width');
77+
url += url.includes('?') ? '&' : '?';
78+
if (sizes && 'raw' in sizes) {
79+
url += 'imgSizes=' + encodeURIComponent(sizes.raw) + '&';
80+
}
81+
if (width && 'raw' in width) {
82+
url += 'imgWidth=' + encodeURIComponent(width.raw) + '&';
83+
}
84+
url += 'enhanced';
8185
}
82-
url += 'enhanced';
8386

84-
if (OPTIMIZABLE.test(url)) {
85-
// resolves the import so that we can build the entire picture template string and don't
86-
// need any logic blocks
87-
const resolved_id = (await opts.plugin_context.resolve(url, filename))?.id;
88-
if (!resolved_id) {
89-
const file_path = url.substring(0, url.indexOf('?'));
90-
if (existsSync(path.resolve(opts.vite_config.publicDir, file_path))) {
91-
throw new Error(
92-
`Could not locate ${file_path}. Please move it to be located relative to the page in the routes directory or reference it beginning with /static/. See https://vitejs.dev/guide/assets for more details on referencing assets.`
93-
);
94-
}
87+
// resolves the import so that we can build the entire picture template string and don't
88+
// need any logic blocks
89+
const resolved_id = (await opts.plugin_context.resolve(url, filename))?.id;
90+
if (!resolved_id) {
91+
const query_index = url.indexOf('?');
92+
const file_path = query_index >= 0 ? url.substring(0, query_index) : url;
93+
if (existsSync(path.resolve(opts.vite_config.publicDir, file_path))) {
9594
throw new Error(
96-
`Could not locate ${file_path}. See https://vitejs.dev/guide/assets for more details on referencing assets.`
95+
`Could not locate ${file_path}. Please move it to be located relative to the page in the routes directory or reference it beginning with /static/. See https://vitejs.dev/guide/assets for more details on referencing assets.`
9796
);
9897
}
98+
throw new Error(
99+
`Could not locate ${file_path}. See https://vitejs.dev/guide/assets for more details on referencing assets.`
100+
);
101+
}
99102

103+
if (OPTIMIZABLE.test(url)) {
100104
let image = images.get(resolved_id);
101105
if (!image) {
102106
image = await process(resolved_id, opts);
103107
images.set(resolved_id, image);
104108
}
105109
s.update(node.start, node.end, img_to_picture(content, node, image));
106110
} else {
107-
// e.g. <img src="./foo.svg" /> => <img src={__IMPORTED_ASSET_0__} />
108111
const name = '__IMPORTED_ASSET_' + imports.size + '__';
109-
const { start, end } = src_attribute;
110-
// update src with reference to imported asset
111-
s.update(
112-
is_quote(content, start - 1) ? start - 1 : start,
113-
is_quote(content, end) ? end + 1 : end,
114-
`{${name}}`
115-
);
116-
// update `enhanced:img` to `img`
117-
s.update(node.start + 1, node.start + 1 + 'enhanced:img'.length, 'img');
112+
const metadata = await sharp(resolved_id).metadata();
113+
const new_markup = `<img ${serialize_img_attributes(content, node.attributes, {
114+
src: `{${name}}`,
115+
width: metadata.width || 0,
116+
height: metadata.height || 0
117+
})} />`;
118+
s.update(node.start, node.end, new_markup);
118119
imports.set(original_url, name);
119120
}
120121
}
@@ -175,15 +176,6 @@ export function image(opts) {
175176
};
176177
}
177178

178-
/**
179-
* @param {string} content
180-
* @param {number} index
181-
* @returns {boolean}
182-
*/
183-
function is_quote(content, index) {
184-
return content.charAt(index) === '"' || content.charAt(index) === "'";
185-
}
186-
187179
/**
188180
* @param {string} resolved_id
189181
* @param {{

packages/enhanced-img/test/Input.svelte

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@
4545

4646
<enhanced:img {src} alt="attribute shorthand test" />
4747

48-
<enhanced:img src="./foo.svg" alt="svg test" />
49-
5048
{#each images as image}
5149
<enhanced:img src={image} alt="opt-in test" />
5250
{/each}

packages/enhanced-img/test/Output.svelte

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
<script lang="ts">
2-
import __IMPORTED_ASSET_0__ from "./foo.svg";
3-
1+
<script lang="ts">
42
53
import manual_image1 from './no.png';
64
@@ -50,8 +48,6 @@
5048
</picture>
5149
{/if}
5250

53-
<img src={__IMPORTED_ASSET_0__} alt="svg test" />
54-
5551
{#each images as image}
5652
{#if typeof image === 'string'}
5753
<img src={image.img.src} alt="opt-in test" width={image.img.w} height={image.img.h} />

packages/enhanced-img/test/preprocessor.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ it('Image preprocess snapshot test', async () => {
3737
// Make imports readable
3838
const ouput = processed.code.replace(/import/g, '\n\timport');
3939

40-
expect(ouput).toMatchFileSnapshot('./Output.svelte');
40+
await expect(ouput).toMatchFileSnapshot('./Output.svelte');
4141
});
4242

4343
it('parses a minimized object', () => {

playgrounds/basic/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"@sveltejs/adapter-static": "workspace:*",
2323
"@sveltejs/adapter-vercel": "workspace:*",
2424
"@sveltejs/amp": "workspace:*",
25+
"@sveltejs/enhanced-img": "workspace:*",
2526
"@sveltejs/kit": "workspace:*",
2627
"@sveltejs/package": "workspace:*",
2728
"@sveltejs/vite-plugin-svelte": "^5.0.1",

playgrounds/basic/src/routes/+page.svelte

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,9 @@
77
<h1>Welcome to SvelteKit</h1>
88

99
2 + 2 = {data.sum}
10+
11+
<h2>Pages:</h2>
12+
13+
<ul>
14+
<li><a href="images">images</a></li>
15+
</ul>

playgrounds/basic/src/routes/about/+page.svelte

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<div><a href="/">home</a></div>
2+
<br />
3+
<div>
4+
<enhanced:img
5+
src="./state-of-js-chart.png"
6+
style="width:700px; height:auto"
7+
alt="Svelte Collective"
8+
/>
9+
</div>
10+
<br />
11+
<div><enhanced:img src="./svelte-logo.svg" alt="Svelte" /></div>

0 commit comments

Comments
 (0)