fix: warn rather than crash when non-enhanced image dynamically passed to `enhanced...
authorBen McCann <322311+benmccann@users.noreply.github.com>
Fri, 7 Nov 2025 12:14:50 +0000 (04:14 -0800)
committerGitHub <noreply@github.com>
Fri, 7 Nov 2025 12:14:50 +0000 (04:14 -0800)
.changeset/poor-heads-fix.md [new file with mode: 0644]
documentation/docs/40-best-practices/07-images.md
packages/enhanced-img/src/vite-plugin.js
packages/enhanced-img/test/Output.svelte
packages/enhanced-img/test/apps/basics/src/routes/+page.svelte

diff --git a/.changeset/poor-heads-fix.md b/.changeset/poor-heads-fix.md
new file mode 100644 (file)
index 0000000..aacdc86
--- /dev/null
@@ -0,0 +1,5 @@
+---
+'@sveltejs/enhanced-img': patch
+---
+
+fix: warn rather than crash when non-enhanced image dynamically passed to `enhanced:img`
index eb54ec4884199e29b78aa645907418f35d81bdea..4ba8d24204939d8c77636b3a1fa4a0fcc3148d1a 100644 (file)
@@ -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: {
@@ -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:
index e9eced264413920d846d2a5c93eb54186d0847e7..2c4599057c36fe50e305ce56908e0a562b308877 100644 (file)
@@ -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);
@@ -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) {
@@ -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(' ');
@@ -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}`;
 }
index d8c18c1ad88fe058af40208b91ad080dd45f3b4b..431adaf72d27bf86316f1c37630231175766cc51 100644 (file)
 <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]}
 
 {#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]}
 
 {#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]}
index 1370a8020d4f8adfeea96a3ef838aede956e248f..c1b83b304a846107a73896630aa16fc18cfbe500 100644 (file)
@@ -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" />