9
votes

I have about 80 custom SVG icons that I'm importing into a Svelte front-end app. Building on https://github.com/sveltejs/template, it's built with Rollup and includes Typescript, Tailwind, and all the modern goodies.

enter image description here

The dilemma is how to add the icons into the app. As SVGs, the icons are short XML text strings that do not exceed 2kB.

Option 1: as image assets

  1. Upload all the icons as foo.svg into public/assets/icons.
  2. Create a svelte component <Icon type="foo' /> that displays an the icon using <img src="foo.svg>.

This approach means that the icons are not part of the code.

Benefits: icons can be dynamically loaded by frontend code on demand. No need to bundle all icons into app code.

Cons: slow page load if there are a lot of new icons, and the browser has to fetch a dozen 1kB files. Deploying the app as a PWA means we need to manually tell it to cache the icons beforehand.

Option 2: as part of the app build

  1. Use something like https://github.com/cristovao-trevisan/svelte-icon or https://github.com/codefeathers/rollup-plugin-svelte-svg to directly import each icon into code: import Home from './icons/home.svg';
  2. Create a svelte component that selects the right imported component or SVG string and displays it.

Here, the icons are bundled as text strings with the app itself.

Benefits: Icons are delivered as part of the app bundle. Caching is unnecessary. Possible to dynamically modify some of the icon code e.g. colors, viewBox, etc on load.

Cons: It's unnecessary to include all icons in the app to reduce time to first byte. Can't do bundle splitting, etc. without adding more complexity. Makes the rendering slower because Javascript code needs to first turn a string into an SVG instead of just loading an image.

Questions

  • It seems that building icons in tthe app is a better way from this analysis, but have I missed something?
  • Does the calculus change if the "icons" are detailed images that are 50-100kB instead of the tiny strings here?
3
Option 3: create the SVG client-side with a <svg-icon> Custom Element from a string holding only d path data; see iconmeister.github.io - I never did the Svelte version because Svelte handles the <svg-icon> native element just fine. I have converted over 7000 icons from different IconSets - The JS code is only 800 Bytes GZippedDanny '365CSI' Engelman
PS. if you have 50-100kB for "icons" there is something wrong with the "designer". I did 52 Playingcards in 16kB and 300+ Country flags in 29kBDanny '365CSI' Engelman
I always just inline them (option 2) but make sure to optimize all your svg, those sizes are indeed very large, you can use a tool like svgomg for the optimizing. If the icons come from a designer their tool might also be able to export them, just inspect the svg and if you see lot's of crap instead of plain paths it's a sign they are not optimized.Stephane Vanraes
Focus with Mobile 3G, speed must go first (I prefer the option 3 by Danny, like other said the size seems big for just svg)daison12006013
@StephaneVanraes are you saying that 1kb is large or 50kb is large?Andrew Mao

3 Answers

5
votes

The following approach has these advantages:

  • One central point to maintain all your icons for your app
  • Reduced network requests for fetching SVG icons
  • Reusable icons throughout the app without having duplicate svg elements

Have a dedicated Icon.svelte component setup like this:

<script>
    export let name;
    export let width = "1rem";
    export let height = "1rem";
    export let focusable = false;
    let icons = [
        {
          box: 24,
          name: "save",
          svg: `<g stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><path d="M17 21v-8H7v8"/><path d="M7 3v5h8"/></g>`
        },
        {
          box: 32,
          name: "trash",
          svg: `<path d="M12 12h2v12h-2z" /><path d="M18 12h2v12h-2z" /><path d="M4 6v2h2v20a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8h2V6zm4 22V8h16v20z" /><path d="M12 2h8v2h-8z" />`
        }
    ];
    let displayIcon = icons.find((e) => e.name === name);
</script>
<svg
  class={$$props.class}
  {focusable}
  {width}
  {height}
  viewBox="0 0 {displayIcon.box} {displayIcon.box}">{@html displayIcon.svg}</svg>

You can then use it like so:

<Icon name="trash" class="this-is-optional" />
2
votes

You can just change the file extension to .svelte and import an SVG as a normal component.

1
votes

A working solution is to hold the SVGs in a separate IconService.js:

export const chevron_down = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"feather feather-chevron-down\"><polyline points=\"6 9 12 15 18 9\"></polyline></svg>";

export const chevron_right = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"feather feather-chevron-right\"><polyline points=\"9 18 15 12 9 6\"></polyline></svg>";

This can easily be imported and used within svelte @html function.

<script>
    import {chevron_down, chevron_right} from 'IconService';
</script>

{@html chevron_down}