#Is there an SVG sprite plugin (Vite) for Preact and Astro?

1 messages · Page 1 of 1 (latest)

ionic steppe
#

I want to use the same interface to import svg into both astro and tsx files. I can import the icons as jsx, but it seems inefficient for the client code (see https://twitter.com/_developit/status/1382838799420514317?lang=en).

It would be ideal to import them with <use link />, although I don't know how to do this for svg imports individually without loading the full sprite list (per page, component):

<!-- sprite -->
<svg style="display: none;">
    <defs>
        <symbol id="arrow-icon">
            <path d=""></path>
        </symbol>
    </defs>

    <use href="#arrow-icon"></use>
</svg>

<!-- usage -->
<svg>
    <use href="#arrow-icon" ></use>
</svg>

Here's one such plugin, unfortunately it doesn't work with Vite 4 (I guess):
https://github.com/meowtec/vite-plugin-svg-sprite

import createSvgSpritePlugin from 'vite-plugin-svg-sprite';

const config = {
  plugins: [
    createSvgSpritePlugin({
      symbolId: 'icon-[name]-[hash]',
    }),
  ],
}
import appIconId from './path/to/icons/app.svg';

// react or vue component, as you want
export default function App() {
  return (
    <svg>
      <use
        xlinkHref={`#${appIconId}`}
      />
    </svg>
  );
}

Please don't import SVGs as JSX. It's the most expensive form of sprite sheet: costs a minimum of 3x more than other techniques, and hurts both runtime (rendering) performance and memory usage.

This bundle from a popular site is almost 50% SVG icons (250kb), and most are unused.

Likes

1584

Retweets

292

GitHub

SVG sprite plugin for vite - GitHub - meowtec/vite-plugin-svg-sprite: SVG sprite plugin for vite

ionic steppe
#

The implementation of astro-icon (<Sprite.Prodiver />, <Sprite />) is the perfect solution, unfortunately we cannot import astro components in other frameworks 😦

#

To support this, we can import the framework component into astro file and pass icons through named slots, but that's not ideal..

analog thicket
#

You could declare the icons for each page in that page's astro file

<div hidden>
  <Sprite name="mdi:foo"/>
  <Sprite name="mdi:bar"/>
  ...
</div>

And then just manually write your <use> references to the included icons where you need them in Preact. You could wrap this in a Preact component that takes the same props as astro-image's Sprite for ease of use.

An alternative to declaring the icons as hidden HTML might be to import trackSprite() from astro-icon/lib/context.ts and call it in the frontmatter of the Astro page for each icon:

---
import { trackSprite } from 'astro-icon/lib/context.ts';
trackSprite(Astro.request, "mdi:foo");
trackSprite(Astro.request, "mdi:bar");
---
<!-- astro template HTML here -->

Haven't tested that, though--that's just a guess based on what's in the package source code.

If it does work, maybe someone should open a PR for exposing trackSprite() as part of the public API.

ionic steppe
#

It ended up creating an Icons.astro file in the components folder that contains a Pagination.tsx with no children prop specified, just some attribute properties. And it doesn't render the <Sprite />, but actually passes it as a child component which the <Sprite.Provider /> uses to recognize which svg it should import to create the sprite! I don't know if this is a bug.. but it works 🙂

projects/index.astro:

<Pagination
    total={pageInfo.offsetPagination.total}
    pageSize={size}
    currentPageIndex={page}
>
    <Icons />
</Pagination>

Icons.astro:

---
import { Sprite } from 'astro-icon';
---

<Sprite name="chevron-left" />
<Sprite name="chevron-right" />

Pagination.tsx:

export default function Pagination({ ...props }) {
  ... 

    return (
        <PaginationPrimitive.Root
            store={{ total, pageSize, boundaryCount, siblingCount, currentPageIndex }}
        >
            <PaginationPrimitive.Controls aria-label="Select page">
                <PaginationPrimitive.Navigation
                    type="previous"
                >
                    <svg width="15" height="15">
                        <use href="#astroicon:chevron-left" />
                    </svg>
                </PaginationPrimitive.Navigation>

                ...
            </PaginationPrimitive.Controls>
        </PaginationPrimitive.Root>
    );
}
versed folio
ionic steppe
versed folio
#

@ionic steppe you mean your own set of SVGs?

versed folio
#

You can do that

// astro.config.mjs
import Icons from 'unplugin-icons/vite';
import { FileSystemIconLoader } from 'unplugin-icons/loaders';

export default defineConfig({
vite: {
    ...
    plugins: [
      Icons({
        compiler: 'vue3',
        customCollections: {
          // https://github.com/antfu/unplugin-icons#custom-icons
          myAwesomeIcons: FileSystemIconLoader('src/icons', (svg) =>
            svg.replace(/^<svg /, '<svg fill="currentColor" ')
          ),
        },
      }),
    ],
  },
})
// some .astro file (same goes for react or vue)
---
import BedIcon from '~icons/myAwesomeIcons/bed';
---

<BedIcon class="w-6 h-6 [&_*]:fill-white inline mr-2" />
ionic steppe
versed folio
#

I'm not sure, I would say no in client-side. In mi case I just hydrate a few components with icons, its a totally different thing when doing a full page hydration

analog thicket