The Issue
I know this is an old question, but unfortunately, I also run into the same bug, and the answer here doesn't help a lot.
- using a
<img>
is great if the svg styles are self-contained, but once you need to size or colour them relative to the parent element (like currentColor
this stop working)
- creating a new Svelte file can be great, but I prefer that my assets are in one folder as normal.
why?
- for readable purposes:
.svg
/.png
/.jpg
for assets. and real Components with .svelte
- performance purposes: https://stackoverflow.com/a/75191716/17716837
- time consuming: for every asset to have to add: you need manually create a new file
.svelte
, and paste the .svg
code. this is easy for 2/3 of icons but what happens if you have 50/80+ icons? is time-consuming.
- using an external library for this isn't necessary,
because we can implement this by ourselves using less code and native vite/svelte code.
So let's do it below
Solution
As I said, guys don't install that library because sveltekit
+vite
handles that natively easily (unless you need to)
now in lib/
create a folder called assets/
where you will insert all your .svg
/.png
/.jpg
(not inside static/
for performance reasons, because if inside lib
vite bundler will handle caching)
let's say we want to import myIcon.svg:
<script>
import myIcon from "$lib/assets/myIcon.svg?raw"
</script>
{@html myIcon}
?raw
is returning us the text inside the file (see https://vitejs.dev/guide/features.html#static-assets)
instead of the URL link, like it was happening before (for default assets return have ?url
in vite)
As you see, use 2 lines of native svelte code to make it work fine.
this works also when you nest your svg inside another and you are using currentColor
in most cases this @html is great,
But if your images come from input then don't use @html or you can be hacked
(or use it but with the input values sanitized)
EDIT:
- if you want to make this like a component library use this code for your component:
<script>
export let src;
$: isSvg = !!src.endsWith(".svg"); // we use $: because you may dynamically change the src so the component needs to be reactive. if you are sure that it won't be changed it will be great to use instead `const`
$: svgRoute = isSvg ? src.replace(".svg", ".svg?raw") : null; // you can also do `${src}?raw`
</script>
{#if isSvg}
{#await import(svgRoute)}
<div>loading...</div> <!-- default fallback, can be a loading spinner or anything else that the user will see while waiting for the loading -->
{:then value}
{@html value.default} <!-- the svg code will be inject here -->
{/await}
{:else}
<img src={Gsrc} /> <!-- if the src is only a normal photo then use native html `<img>`'s src attribute -->
{/if}
- if you want to simplify even more this
import myIcon from "$lib/assets/myIcon.svg?raw"
you can use my other answer on that topic
How can I simplify my imports when using SvelteKit with Vite bundler, like $lib/?
so by using that component it will be like this:
<script>
import Icon from "$lib/Icon.svelte"
</script>
<Icon src="$assets/foo.svg" />
<Icon src="$assets/bar.svg" />
<Icon src="$assets/hello.png" />
<div class="text-blue-500">
<Icon src="$assets/nested.svg" />
</div>
EDIT: ⚠️ fix of latest vitejs's bug
lately the above solution won't work anymore when using the npm run build
by throwing this error:
TypeError: Failed to fetch dynamically imported module
https://github.com/vitejs/vite/issues/11804
so use this instead for the svg part
<script lang="ts">
export let src: string;
$: isSvg = !!src.endsWith(".svg");
$: svgRoute = `${src}?raw`;
function fetchSvg(svgRoute: string) {
return new Promise((resolve) => {
fetch(svgRoute)
.then(response => response.text())
.then(svg => resolve(svg))
})
}
</script>
{#if isSvg}
{#await fetchSvg(svgRoute)}
<div>loading...</div>
{:then value}
{@html value}
{/await}
{:else}
<img src={Gsrc} />
{/if}