Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/poor-heads-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/enhanced-img': patch
---

fix: warn rather than crash when non-enhanced image dynamically passed to `enhanced:img`
4 changes: 3 additions & 1 deletion documentation/docs/40-best-practices/07-images.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ You can also use [Vite's `import.meta.glob`](https://vitejs.dev/guide/features.h
```svelte
<script>
const imageModules = import.meta.glob(
'/path/to/assets/*.{avif,gif,heif,jpeg,jpg,png,tiff,webp,svg}',
'/path/to/assets/*.{avif,gif,heif,jpeg,jpg,png,tiff,webp}',
{
eager: true,
query: {
Expand All @@ -99,6 +99,8 @@ You can also use [Vite's `import.meta.glob`](https://vitejs.dev/guide/features.h
{/each}
```

> [!NOTE] svg images are currently only supported statically

### Intrinsic Dimensions

`width` and `height` are optional as they can be inferred from the source image and will be automatically added when the `<enhanced:img>` tag is preprocessed. With these attributes, the browser can reserve the correct amount of space, preventing [layout shift](https://web.dev/articles/cls). If you'd like to use a different `width` and `height` you can style the image with CSS. Because the preprocessor adds a `width` and `height` for you, if you'd like one of the dimensions to be automatically calculated then you will need to specify that:
Expand Down
84 changes: 51 additions & 33 deletions packages/enhanced-img/src/vite-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,13 @@ export function image_plugin(imagetools_plugin) {
// this must come after the await so that we don't hand off processing between getting
// the imports.size and incrementing the imports.size
const name = imports.get(original_url) || '__IMPORTED_ASSET_' + imports.size + '__';
if (!metadata.width || !metadata.height) {
console.warn(`Could not determine intrinsic dimensions for ${resolved_id}`);
}
const new_markup = `<img ${serialize_img_attributes(content, node.attributes, {
src: `{${name}}`,
width: metadata.width || 0,
height: metadata.height || 0
width: metadata.width,
height: metadata.height
})} />`;
s.update(node.start, node.end, new_markup);
imports.set(original_url, name);
Expand Down Expand Up @@ -258,8 +261,8 @@ function get_attr_value(node, attr) {
* @param {import('../types/internal.js').Attribute[]} attributes
* @param {{
* src: string,
* width: string | number,
* height: string | number
* width?: string | number,
* height?: string | number
* }} details
*/
function serialize_img_attributes(content, attributes, details) {
Expand All @@ -283,21 +286,23 @@ function serialize_img_attributes(content, attributes, details) {
}
}
}
if (!user_width && !user_height) {
attribute_strings.push(`width=${details.width}`);
attribute_strings.push(`height=${details.height}`);
} else if (!user_width && user_height) {
attribute_strings.push(
`width=${Math.round(
(stringToNumber(details.width) * user_height) / stringToNumber(details.height)
)}`
);
} else if (!user_height && user_width) {
attribute_strings.push(
`height=${Math.round(
(stringToNumber(details.height) * user_width) / stringToNumber(details.width)
)}`
);
if (details.width && details.height) {
if (!user_width && !user_height) {
attribute_strings.push(`width=${details.width}`);
attribute_strings.push(`height=${details.height}`);
} else if (!user_width && user_height) {
attribute_strings.push(
`width=${Math.round(
(stringToNumber(details.width) * user_height) / stringToNumber(details.height)
)}`
);
} else if (!user_height && user_width) {
attribute_strings.push(
`height=${Math.round(
(stringToNumber(details.height) * user_width) / stringToNumber(details.width)
)}`
);
}
}

return attribute_strings.join(' ');
Expand Down Expand Up @@ -358,29 +363,42 @@ function to_value(src) {
*/
function dynamic_img_to_picture(content, node, src_var_name) {
const attributes = node.attributes;
const index = attributes.findIndex(
(attribute) => 'name' in attribute && attribute.name === 'sizes'
);
/**
* @param attribute_name {string}
*/
function index(attribute_name) {
return attributes.findIndex(
(attribute) => 'name' in attribute && attribute.name === attribute_name
);
}
const size_index = index('sizes');
const width_index = index('width');
const height_index = index('height');
let sizes_string = '';
if (index >= 0) {
sizes_string = ' ' + content.substring(attributes[index].start, attributes[index].end);
attributes.splice(index, 1);
if (size_index >= 0) {
sizes_string =
' ' + content.substring(attributes[size_index].start, attributes[size_index].end);
attributes.splice(size_index, 1);
}

const details = {
src: `{${src_var_name}.img.src}`,
width: `{${src_var_name}.img.w}`,
height: `{${src_var_name}.img.h}`
};

return `{#if typeof ${src_var_name} === 'string'}
<img ${serialize_img_attributes(content, attributes, details)} />
{#if import.meta.DEV && ${!width_index && !height_index}}
{${src_var_name}} was not enhanced. Cannot determine dimensions.
{:else}
<img ${serialize_img_attributes(content, attributes, {
src: `{${src_var_name}}`
})} />
{/if}
{:else}
<picture>
{#each Object.entries(${src_var_name}.sources) as [format, srcset]}
<source {srcset}${sizes_string} type={'image/' + format} />
{/each}
<img ${serialize_img_attributes(content, attributes, details)} />
<img ${serialize_img_attributes(content, attributes, {
src: `{${src_var_name}.img.src}`,
width: `{${src_var_name}.img.w}`,
height: `{${src_var_name}.img.h}`
})} />
</picture>
{/if}`;
}
21 changes: 18 additions & 3 deletions packages/enhanced-img/test/Output.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@
<picture><source srcset="/1 1440w, /2 960w" type="image/avif" /><source srcset="/3 1440w, /4 960w" type="image/webp" /><source srcset="5 1440w, /6 960w" type="image/png" /><img src="/7" alt="absolute path test" width=1440 height=1440 /></picture>

{#if typeof src === 'string'}
<img src={src.img.src} alt="attribute shorthand test" width={src.img.w} height={src.img.h} />
{#if
import.meta.DEV && false}
{src} was not enhanced. Cannot determine dimensions.
{:else}
<img src={src} alt="attribute shorthand test" />
{/if}
{:else}
<picture>
{#each Object.entries(src.sources) as [format, srcset]}
Expand All @@ -50,7 +55,12 @@

{#each images as image}
{#if typeof image === 'string'}
<img src={image.img.src} alt="opt-in test" width={image.img.w} height={image.img.h} />
{#if
import.meta.DEV && false}
{image} was not enhanced. Cannot determine dimensions.
{:else}
<img src={image} alt="opt-in test" />
{/if}
{:else}
<picture>
{#each Object.entries(image.sources) as [format, srcset]}
Expand All @@ -63,7 +73,12 @@

{#each images as _, i}
{#if typeof get_image(i) === 'string'}
<img src={get_image(i).img.src} alt="opt-in test" width={get_image(i).img.w} height={get_image(i).img.h} />
{#if
import.meta.DEV && false}
{get_image(i)} was not enhanced. Cannot determine dimensions.
{:else}
<img src={get_image(i)} alt="opt-in test" />
{/if}
{:else}
<picture>
{#each Object.entries(get_image(i).sources) as [format, srcset]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
import logo from './logo.png?enhanced';
</script>

<enhanced:img id="playwright" src="./playwright-logo.svg" alt="Playwright logo" />
<!-- standard image -->
<enhanced:img id="birds" src="./birds.jpg" alt="birds" />

<!-- svg over inline size -->
<enhanced:img id="playwright" src="./playwright-logo.svg" alt="Playwright logo" />

<!-- dynamic image -->
<enhanced:img id="logo" src={logo} alt="Svelte logo" />
Loading