vitesveltesvelte-5

images paths in the build when they are provided from a JSON


I have a Svelte 5 app (not SvelteKit) where the data, including images paths, is provided from JSON. Images are in src/lib directory, as advised in the official Svelte doc.

data.json:

[
    {
        "title": "tile 1",
        "images": [
            { "src": "/src/lib/images/tile1a.jpg" },
            { "src": "/src/lib/images/tile1b.jpg" }
        ]
    },
    {
        "title": "tile 2",
        "images": [
            { "src": "/src/lib/images/tile2a.jpg" },
            { "src": "/src/lib/images/tile2b.jpg" }
        ]
    }
]

The parent component loads the data and pass it to the child components:

<script lang="ts">
import data from "$lib/data.json"
import ChildComponent from "$lib/components/ChildComponent.svelte"
</script>

<main>
    {#each data as tile}
        <ChildComponent title={tile.title} images={tile.images} />
    {/each}
</main>

And the child components use the images files paths as img src attribute:

<script lang="ts">

interface Props {
    title: string
    images: { src: string }[]
}
const { title, images }: Props = $props()

</script>

<div class="tile">
    <span>{title}</span>
    {#each images as i}
        <img src='{i.src}' />
    {/each}
</div>

This works as intended when running with Vite in dev mode (vite dev), as the images paths exist.

But when running a built version of the app (vite build, then vite preview), images are not found, since their paths is not converted to the dist folder.

I guess the images should be imported as Svelte imports (import Image from '/src/lib/images/tile1a.jpg') so that they can be resolved in the distributable build (dist) directory, but how can one do that with dynamic paths?


Solution

  • You can import the images via glob import and use that mapping to look up the asset URL. Something along the lines of:

    <script lang="ts">
      import data from "$lib/data.json";
      const imageMap: Record<string, string> = import.meta.glob(
        "$lib/images/*",
        { eager: true, import: "default" },
      );
    </script>
    
    <main>
      {#each data as tile}
        {@const images = tile.images.map(i => ({ ...i, src: imageMap[i.src] }))}
        <ChildComponent title={tile.title} {images} />
      {/each}
    </main>