@svelte-put/inline-svg
GithubCompatible with or powered directly by Svelte runes.
Introduction
Existing solutions for inline SVGs in Svelte land often rely on component, which proves painful when it comes to custom styling or event handling. This package attempts to achieve a more minimal alternative using Svelte action (runtime) and Svelte preprocessor (compile time).
@svelte-put/preprocessor-inline-svg
has been merged into version 4 of @svelte-put/inline-svg
for a single coherent package. See Migration Guides if you previously used Svelte preprocessor for compile time solution.
Installation
npm install --save-dev @svelte-put/inline-svg
pnpm add -D @svelte-put/inline-svg
yarn add -D @svelte-put/inline-svg
Runtime - Dynamic SVGs - Svelte Action
This strategy is useful when:
- you don't know in advance what SVGs to inline until runtime (after app/site is loaded or data is fetched in browser), or
- if your SVG is large in size and only conditionally rendered.
For static icons and pictograms, consider the compile-time strategy instead.
Quick Start
The Svelte logo SVG on the right is dynamically fetched via network at runtime upon page load. Notice in the source code below, only width
(or height
) needs to be specified. By default, inlineSvg
will calculate the other dimension the keep the aspect ratio.
<script>
import { inlineSvg } from '@svelte-put/inline-svg';
let { src = 'https://raw.githubusercontent.com/sveltejs/branding/master/svelte-logo.svg' } =
$props();
</script>
<!-- add attributes and styling as with any other HTML element -->
<svg use:inlineSvg={src} width="100" class="svelte" />
<style>
svg.svelte {
filter: drop-shadow(0 0 0.5rem var(--color-svelte));
}
</style>
inlineSvg
only works if used on <svg>
for obvious reason.
Attributes and Inner HTML
Attributes provided to the svg
element where inlineSvg
is placed on will replace existed ones from the original SVG. On the contrary, its inner HTML will be completely replaced.
Take the following SVG as an example:
<svg viewBox="0 0 24 24" width="24" height="24" stroke-width="2">
<!-- truncated original svg innerHTML -->
</svg>
And inlineSvg
is used as follows:
<svg use:inlineSvg={'https://example.com/original.svg'} height="16" stroke-width="1">
<!-- some innerHTML -->
</svg>
The resulting SVG at runtime will be:
<svg viewBox="0 0 24 24" width="16" height="16" stroke-width="1">
<!-- truncated original svg innerHTML -->
</svg>
Customization
The inlineSvg
action takes either a string as the remote SVG url, or a config object with the following interface:
/** verbose config for `inlineSvg` action */
export interface InlineSvgActionConfig {
/** svg remote URI */
src: string;
/** cache policy for use in fetch from svg `src` */
cache?: Request['cache'];
/**
* automatically calculate dimensions from the available attributes
* of both the local SVG element (on which action is used) and the remote SVG
*
* For example, if you specify only `width` to the local SVG element, the
* height will automatically be calculated from the remote SVG
*
* For this to work, width & height must be "extractable" from the remote element,
* that is, the remote SVG must either have the `viewBox` or both `width` and `height` attributes that
* is in the same unit.
*/
autoDimensions?: boolean;
/** optionally transform the SVG string fetched from remote source before inlining */
transform?: (svg: string) => string;
}
Compile Time - Static SVGs - Svelte Preprocessor
This strategy is useful for SVGs that live in your repo as static assets. It only runs at compile time by Svelte preprocessor (via Vite plugin) during development, and therefore has zero runtime footprint. The idea is to enable the following...
<svg inline-src="google/info"> <!-- innerHTML inlined at compile time --> </svg>
...which allows additional styling and attributes to be added idiomatically as with any other HTML element. This is different from current solutions that I know of for inlining SVGs in Svelte land, which require either runtime logics or a component-oriented strategy.
Alternatively, for dynamic SVGs that are fetched at runtime, consider using the runtime strategy instead.
This library makes no implication that you should or should not use SVG to render icons. For some cases, it is helpful to do so, especially in conjunction with customizable color scheme. For others, different strategies such as icon font or CSS-only icons (see Icons in Pure CSS by Anthony Fu) might have more benefits.
Setup
Given the following vite.config.js
and filesystem...
import path from 'path';
import { inlineSvg } from '@svelte-put/inline-svg/vite';
/** @type {import('vite').UserConfig} */
const config = {
plugins: [
inlineSvg([
[
{
directories: 'src/assets/icons',
attributes: {
class: 'icon',
width: '20',
height: '20',
},
},
{
directories: 'src/assets/pictograms',
},
],
]),
sveltekit(),
],
};
export default config;
src/assets
|
|-- icons
|-- svelte.svg
|
|-- google
|-- arrow-right.svg
|-- simpleicons
|-- github.svg
|
|-- pictograms
|-- diagram.svg
...we can now do...
<!-- this will have width="20" height="20" as specified in the config -->
<svg inline-src="svelte" />
<!-- nested -->
<svg inline-src="google/arrow-right.svg" />
<!-- .svg can be omitted -->
<svg inline-src="simpleicons/github" />
<!-- with custom attributes -->
<svg inline-src="diagram" width="100" height="100" />
<!-- alternatively, you can provide a per-case path that is relative to the current source file -->
<svg inline-src="./local-icon.svg" />
<!-- if the source svg is not found for any of the above, an error will be thrown -->
Attributes and Inner HTML
Attributes provided to the svg
element where inline src attribute (inline-src
by default) is specified will replace existed ones from the original SVG. On the contrary, its inner HTML will be completely replaced.
Take the following SVG as an example:
<svg viewBox="0 0 24 24" width="24" height="24" stroke-width="2">
<!-- truncated original svg innerHTML -->
</svg>
And inlineSvg
is used as follows:
<svg inline-src="./local-icon.svg" height="16" stroke-width="1">
<!-- some innerHTML -->
</svg>
The resulting SVG at runtime will be:
<svg viewBox="0 0 24 24" width="24" height="16" stroke-width="1">
<!-- truncated original svg innerHTML -->
</svg>
Notice that the width
attribute is not automatically calculated for you in the output above. Make sure to provide both dimensions. This behavior differs from the runtime strategy, which automatically calculates the missing dimension to preserve aspect ratio. The rationale is that preprocessor operates at compile time with static SVGs, so it trusts your intention. If you think otherwise, feel free to open a discussion.
If you have a use case where it is useful to append/prepend the innerHTML
of the original SVG rather than replacing it, please raise an issue over at Github. For now, let's keep things simple.
Customization
By default the Vite plugin / Svelte preprocessor can be setup with no config at all, in which case SVG source paths are resolved relative to the Svelte source file the inline src attribute (inline-src
as default) is specified in.
export function inlineSvg(
uSources?: PreprocessorSourceDefinition | PreprocessorSourceDefinition[],
uConfig?: PreprocessorConfig,
): import('svelte/compiler').PreprocessorGroup | import('vite').Plugin;
Note that path alias is not supported! For example, "$lib/src/assets/..."
will not work.
Source Definitions
The first parameter to Vite plugin / Svelte preprocessor can be either a single object, or an array of such (as seen in Setup), helpful for organizing SVG sources into different directories.
/** sources for the inline svg */
export type PreprocessorSourceDefinition = {
/**
* directories relative to which the svg source path will be resolved
*/
directories?: string[] | string;
/**
* default attributes to add to the svg element, will override the attributes from the svg source,
* but be overridden by the attributes from the element itself (in svelte source)
*/
attributes?: Record<string, string>;
};
When source parameter is an array of config objects, the following applies: there can be only one config object without the directories
option, taken as the default config and will be used for all SVG sources not covered by other config objects (all relative SVG paths). If multiple config objects without directories
are provided, an error will be thrown during development. If none is provided, the internal default config is used.
During build, each SVG source will then be searched top down in the config until a match is found, or else an error will be thrown. Relative SVGs (relative to current Svelte source file) always has the highest priority and will use the default config as described above.
Preprocessor Options
The second parameter to the Vite plugin / Svelte preprocessor provides customization to the processor itself. Their corresponding interfaces are as follow:
/** global options for configuring behaviors of the inline-svg preprocessor */
export type PreprocessorConfig = {
/** attribute to get the svg source from, default to `inline-src` */
inlineSrcAttributeName?: string;
/** whether to keep the inline src attribute after compilation, default to `false` */
keepInlineSrcAttribute?: boolean;
};
/** configuration for the vite plugin that wraps inline-svg preprocessor */
export type VitePluginConfig = PreprocessorConfig & {
/**
* output path for generated type definition for inline src attribute.
* Defaults to `null` (no generation).
* Set to `true` to use the default `src/preprocess-inline-svg.d.ts` path.
*/
typedef?: string | null | true;
};
Typescript Support
Specify typedef
option to let Vite plugin automatically generate type definition for the inline src attribute. This is useful to enable type checking and auto-completion in your editor.
import path from 'path';
import { inlineSvg } from '@svelte-put/inline-svg/vite';
/** @type {import('vite').UserConfig} */
const config = {
plugins: [
inlineSvg([
[/** truncated source config as in Setup */],
{ typedef: true },
]),
sveltekit(),
],
};
export default config;
/** DO NOT EDIT! This file is generated by @svelte-put/inline-svg vite plugin */
declare module 'svelte/elements' {
export interface SVGAttributes {
'inline-src'?:
`./${string}`
| `../${string}`
| 'svelte'
| 'google/arrow-right'
| 'simpleicons/github'
| 'diagram';
}
}
export {};
src/assets
|
|-- icons
|-- svelte.svg
|
|-- google
|-- arrow-right.svg
|-- simpleicons
|-- github.svg
|
|-- pictograms
|-- diagram.svg
As mentioned in Preprocessor Options, when typedef
set to true
, the vite plugin will write the type definition to src/preprocess-inline-svg.d.ts
by default. You can specify a custom path if needed.
Avoid setting inlineSrcAttributeName
to data-*
in this case since it would be "swallowed" by the broader typedef from svelte/elements.
Using Bare Svelte Preprocessor
The wrapper Vite plugin adds some improvements to developer experience by watching the specified directories from your source config and triggering page reload when there is addition/removal/change to them. It also provide optional typedef generation as seen in previous section.
If your setup doesn't allow Vite, however, you can do so by importing the preprocessor directly from @svelte-put/inline-svg/preprocessor
.
import inlineSvg from '@svelte-put/inline-svg/preprocessor';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: [
inlineSvg(/** truncated config */),
// other preprocessors
],
};
export default config;
Limitations
The inlineSvg
preprocessor only works in Svelte markup, i.e in the template part of Svelte files. The following will not work:
<script>
let html = `<svg inline-src="path/icon"></svg>`;
</script>
{@html html}
Similarly, the preprocessor does not support inline src attribute as a variable. The following will not work:
<script>
const icon = 'path/icon';
</script>
<svg inline-src={icon} />
This is because it is difficult for the preprocessor to statically analyze a variable to determine its immutability at compile time, i.e a variable is meant to be changed. In these case, some alternatives are:
- use
if-else
statements to render different svg element with static inline src attribute as literal string, or - use the runtime strategy to dynamically inline SVGs in browser.
If you have an idea for improvements, please raise an issue over at github. Thanks!
Frequently Asked Questions
Q: Why should I care about runtime vs build time?
A: Javascript! Runtime requires Javascript. Without it, users will not see your SVG. On the other hand, build time does the work beforehand, so SVGs are already there in the initial HTML.
Q: When to use which?
A: If you do not know in advance what SVGs to inline, or if your SVG is huge but only conditionally rendered: use Svelte action runtime strategy. Otherwise, especially for static icons and pictograms, use Svelte preprocessor compile-time strategy.
Q: Do I really need this package?
A: No! Use <img>
when possible. My initial use case is to be able to change properties of the SVG, especially color. But that can potentially be done with mask-image. So consider your use case before adding another dependency.
Migration Guides
From Preprocessor
If you are coming @svelte-put/preprocess-inline-svg
, simply update the import path of the preprocessor in your svelte.config.js
:
import inlineSvg from '@svelte-put/preprocess-inline-svg';
import inlineSvg from '@svelte-put/inline-svg/preprocessor';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: [
inlineSvg(/** truncated config */),
// other preprocessors
],
};
export default config;
Additionally, consider switching to the Vite plugin wrapper and enabling typedef
option, if your setup allows it, for better developer experience. For more information, see Using Bare Svelte Preprocessor.
import path from 'path';
import { inlineSvg } from '@svelte-put/inline-svg/vite';
/** @type {import('vite').UserConfig} */
const config = {
plugins: [
inlineSvg([/** truncated source setup */], {
typedef: true,
}),
sveltekit(),
],
};
export default config;
import inlineSvg from '@svelte-put/inline-svg/preprocessor';
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: [
inlineSvg(/** truncated config */),
// other preprocessors
],
};
export default config;
Prior Art
- svelte-inline-svg runs at runtime as Svelte component.
- vite-plugin-svelte-svg runs at build time as Svelte component; svg can be processed with svgo.
- svg-to-svelte: convert SVG files to svelte components.
Happy inlining SVGs! 👨💻