core
core copied to clipboard
[Feature request] Generate SVG sprites as a build step
(Not sure if this is the right place for feature requests. Feel free to move it somewhere more appropriate)
Motivation
Performance (load times, rendering, memory)
This is a visualization of our current production app bundle (no worries! it's split into multiple chunks in case anyone gets dizzy). There's quite a bit going on. The biggest chunk of node_modules/
is @phosphor-icons
which takes up ~300kb of JS, notably (the orange slice). This stems from us using @phosphor-icons/react
.
Instead it would be beneficial if all of this could move over here, into the assets/
part of the app, as a static *.svg
asset that can be cached by browsers and does not clutter JS bundles more than necessary.
As Jason Miller points out correctly
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.
Developer experience
Technically, this is possible today by downloading and using the individual *.svg
files instead of relying on @phosphor-icons/react
. This comes with a significant decline in developer experience though, some of which is described nicely in The "best" way to manage icons in React.js
- SVGs rendered as
<img />
cannot be styled using CSS - Without preloading as a
<link rel="preload" />
, SVGs rendered as<img />
introduce request waterfalls for the initial load. Adding an appropriate preload tag for each icon gets tiring - All of
@phosphor-icons/react
's benefits such as easily importing any of the icons or changing the icon's weight via nothing more than a prop change becomes way more tedious
The blog's proposed solution is to render SVGs as a sprite
<!-- sprite.svg -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<symbol viewBox="0 0 24 24" id="icon-1">
<!-- svg content here -->
</symbol>
<symbol viewBox="0 0 24 24" id="icon-2">
<!-- svg content here -->
</symbol>
</defs>
</svg>
and reference them in a component (taking React as an example)
function Icon({ id, ...props }) {
return (
<svg {...props}>
<use href={`/sprite.svg#${id}`} />
</svg>
);
}
This solves both the performance problems as well as 1.
(can be styled with CSS) and 2.
(only need to preload one file) but still requires manual work to update the SVG sprite by hand each time a new icon is added / the weight is changed / phosphor receives an update where icons are improved/changed.
This can be improved partially by using remix-cli which provides a way to create an SVG sprite by pointing it at a folder containing individual *.svg
files. But most of the above still holds true.
Proposed solution
Putting together all of the above, it would be ideal to have a solution that
- creates an SVG sprite containing all the actually used phosphor-icons
- is as convenient to use as
@phosphor-icons/react
with (partial?) support of all its features
For this to work, I propose new packages @phosphor-icons/react-sprite
and @phosphor-icons/vue-sprite
that both offer a <PhosphorIcon />
much like the <Icon />
component above, offering an interface including all the IconProps & { name: "address-book" | "air-traffic-control" | ... }
.
On top of that, there'll need to be a build process much like the one of TailwindCSS:
Read the contents of *.[jsx, tsx, vue|
files, find all <PhosphorIcon />
s and evaluate their props. Based on that, generate a sprite.svg
that can be imported in one place.
Unfortunately this comes with a rather major caveat I believe: Just as the tailwind build process, this requires to only put static strings as props since otherwise this cannot be statically analyzed without going through the immense pain of somehow evaluating dynamic code. One alternative I can think of is to offer named components <AddressBookThin />
, <AddressBookLight />
and the likes that under the hood all resolve to the same <PhosphorIcon />
for the sole purpose of being able to statically analyze the code for the build process. This still feels kinda meh but all things considered might still be worth it considering the upsides. There may be other ideas I haven't thought of of course.
I understand that this is a passion project built in your guys' free time and something like this isn't done quickly. Just wanted to share some ideas! :)