Search code examples
imagedynamicvitesveltesveltekit

How can I dynamically import images stored in $lib within a component in Svelte?


I'm using an array of dummy data to test how I could display information from an api. In that data, I have a relative image path, for some images I've stored locally in $lib for simplicity in testing.

I loop through each object in this dataset, and create a component with the relative image path passed to the object. In my testing the relative paths are resolving correctly, but I'm unable to load the images dynamically.

After doing a bit of digging, it seems that this is because of the file structure and permissions with $lib. It seems that image import is easy to do if I use a static solution with the images, and just dynamically adjust the relative url, but I want to be able to use Vite's performance improvements when it comes to images. Indeed, Sveltekit documentation recommends storing images in a directory within $lib for this exact reason.

In the component I can manually import each image from $lib and it works as expected like so:

<script>
import img from '$lib/assets/sample-image.jpg'; 
</script>

<img src={img} />

But for the life of me I can't figure out how I can change the import path using a dynamic prop. I imagine it would look something like this:

import img from '$lib/assets/{object.imageUrlProp}';

Or some type of direct access in the image src similar to how static usage would look like:

<img src="$lib/assets/{object.imageUrlProp}" alt="Image" />

But neither of these are valid syntax.

How can I dynamically import images stored in $lib within a component in Svelte?

Edit

Brunnerh's answer works great for files with the same extension. As he explains in the comments, for files with differing extensions, a glob import is required. Here is the specific implementation solution I was able to get working. I had some trouble understanding how to access the modules structure normally passed back from the import, so using as: url simplified that process for me. Here is the specific implementation solution I was able to get working:

General Implementation

<script lang="ts">
const images: any = import.meta.glob(['$lib/assets/**.jpg', '$lib/assets/**.png', '$lib/assets/**.svg'], { eager: true, as: 'url' });
<!--You can change allowed extensions here, or you could do something like '$lib/assets/**' without an extension to allow imports for any file types. The '**' allows for nested folders, so you can replace it with '*' if all assets are directly stored in the assets folder-->
<script>

<!--imageURL will look something like image1.png or folder1/image1.png-->
<img src={images["/src/lib/assets/" + imageURL} />

My Specific Use Case Implementation

I was trying to iterate through JSON data containing relative image URLs:

<script lang="ts">
    import { pages } from '$lib/data/sample_data.json';
    const images: any = import.meta.glob(['$lib/images/**.jpg', '$lib/images/**.png', '$lib/images/**.svg'], { eager: true, as: 'url' });
</script>

{#each Object.entries(pages) as [key, page], index (key)}
    <div class="page">
        <img src={images["/src/lib/images/" + page.featured_image]} alt={page.image_alt} />
        <h2>{page.title}</h2>
    </div>
{/each}

<!--Sample JSON Data-->
{
    "pages": [
        {
            "title": "First Page",
            "featured_image": "image.png",
            "image_alt": "alt for page 1"
        },
        {
            "title": "Second Page",
            "featured_image": "image2.jpg",
            "image_alt": "alt for page 2"
        },
        {
            "title": "Third Page",
            "featured_image": "image3.svg",
            "image_alt": "alt for page 3"
        },
    ]
}

Solution

  • If you only provide the file name, you would need a dynamic import that has the name interpolated into it. You might have to keep the extension separate, so the bundler knows what is being imported.

    {#await import(`$lib/assets/${object.imageUrlProp}.jpg`) then { default: src }}
      <img {src} alt="Image" />
    {/await}
    

    You probably also could do a glob import (with eager) in the parent, then you don't have to await and can pass the full path that can be directly handed off to the image src.

    Should be along the lines of:

    const images = import.meta.glob('$lib/assets/*.jpg', { eager: true });
    
    <Component imgSrc={images[`$lib/assets/${object.imageName}.jpg`]} />
    

    (The image path map could likely also be extracted to a separate module and then imported in the component. Have not tried that, though.)