-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(astro): add Built-in SVG component support #12067
Conversation
🦋 Changeset detectedLatest commit: 7b4a3a9 The changes in this PR will be included in the next version bump. Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is blocked because it contains a major
changeset. A reviewer will merge this at the next release if approved.
2ff0715
to
5f8997c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few suggestions, but this looks really good!
packages/astro/client.d.ts
Outdated
/** | ||
* Bypasses automatic sprite optimization by directly inlinining the SVG | ||
*/ | ||
inline?: boolean |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After sitting with this for a while, inline behavior should be the default. There are a few edge-cases that have come up with dynamic client-side behavior in astro-icon
, so while I still think the optimization is very useful, we should prioritize flexibility by keeping the sprite behavior opt-in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right. I think especially with the SVG imports that developers wouldn't necessarily assume the optimization by default.
My first thought is to offer a "mode" that allows for "sprite" or "inline" ("inline" being the default) that is configurable in the Astro Config. I assume we'll later have some other properties under and "svg" scope such as an "image service" (like svgo).
I have also seen an interesting use-case (natemoo-re/astro-icon#238) that we might be able to support later for exporting symbols instead. This would be fairly trivial with a "mode" and might enable some better DX around framework usages.
I would like to discuss and perhaps iterate a bit on the way we allow developers to opt-in/out of inline/sprite usages. Will bring this up in the Stage 3 RFC.
// Attach file data for SVGs | ||
if (fileMetadata.format === 'svg') { | ||
emittedImage.contents = fileData; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would love @Princesseuh's take on this. Any expected footguns with including the file content for SVGs in the image metadata?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have a lot of SVGs, this can be problematic for SSR since it'll get in your bundle, that's my only concerns
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. Unfortunately that will be unavoidable for this feature.
I think the DX improvement is worth the tradeoff, though!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely outside my realm of expertise. I'll defer to whatever the consensus is here :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something we could do one day (definitely outside of this PR), though it'd make SVG icons somewhat slower in SSR is add a lazy data
property or something, that's essentially async () => await fetch(this.src)
in SSR and something else in SSG so that you can load the data without it being added to the bundle.
packages/astro/test/fixtures/core-image-svg/src/assets/astro.svg
Outdated
Show resolved
Hide resolved
// Attach file data for SVGs | ||
if (fileMetadata.format === 'svg') { | ||
emittedImage.contents = fileData; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have a lot of SVGs, this can be problematic for SSR since it'll get in your bundle, that's my only concerns
5f8997c
to
313caaa
Compare
I have added it behind an experiment flag called @natemoo-re would love your opinion on this approach for configuring the sprite vs inline method |
ad55d4e
to
bf5e98b
Compare
Since we are about to send this PR to As for where, I would start with the Images guide: https://docs.astro.build/en/guides/images/ https://github.com/withastro/docs/blob/5.0.0-beta/src/content/docs/en/guides/images.mdx As for where in the page, I don't exactly know, but I would start with a new paragraph (maybe an h2 heading) and send the PR, then Sarah and team will take it from there, and will help you to craft the content. |
packages/astro/src/assets/runtime.ts
Outdated
/** | ||
* Make sure these IDs are kept on the module-level so they're incremented on a per-page basis | ||
*/ | ||
let ids = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC, if this is on the module-level (and also globally as a singleton), wouldn't this value not be incremented on a per-page basis? If per page, I think it should be bound to the result
from createComponent
as a single result
is created when rendering per page. So maybe we can use a WeakMap here to track the ids per result
, which we can also use it to track the first render instead of using WeakSet<Response>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that WeakMap
seems like a good idea here!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not quite following how to use the WeakMap
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The suggestion here is to use const ids = new WeakMap<SSRResult, number>
instead to track the id
per result
(from createComponent
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having trouble getting this to work. I'll give it a shot again tomorrow. Maybe I'm just tired 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll help you out :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here 6ab0f1a
(#12067)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! I missed that we would still need to keep the counter/ids. I'm curious what the WeakMap
adds in this case since we're still generating the ids by the counter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need the counter
variable there. What I mean is that the WeakMap values are the counter itself that starts from 0 on first assigned.
if (!ids.has(result)) ids.set(result, 0)
const id = ids.get(result)
ids.set(result, id + 1)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll give it a shot but I was having issues previously with this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks really good!
packages/astro/src/assets/runtime.ts
Outdated
/** | ||
* Make sure these IDs are kept on the module-level so they're incremented on a per-page basis | ||
*/ | ||
let ids = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that WeakMap
seems like a good idea here!
@stramel While I was working on the PR, I noticed that you added a new configuration in Astro, which is fine, however the RFC doesn't mention anything about it. We should update the RFC and explain the options. Also, here's some feedback regarding experimental options. There aren't written rules, but in the past I was in your same situation and I went for this route: export default defineConfig({
svg: {
mode: "inline"
},
experimental: {
svg: true
}
}) We still need to have the experimental flag, which stays a boolean every time. Then, you create a new section in the Astro configuration. You're still free to change it as you see fit, because it's part of the experimental feature, however I found that the maintenance of these two separate sections is easier for us maintainers. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you so much for this amazing feature @stramel ! I think people are going to really enjoy this! 🙌
I've done a quick pass on the documentation (changeset and flag docs) and you'll find some suggestions below for your consideration!
I know Nate has a docs PR going, and it seems like the mode
property is newer than that, so we'll just make sure that we've got everything we need in the docs PR, too, before docs totally signs off on this! 🚀
* - `inline`: Astro will inline the SVG content into your HTML output. | ||
* - `sprite`: Astro will generate a sprite sheet with all imported SVG files. | ||
* | ||
* When using 'sprite' mode, it is important to note that the symbol definition will be included with the first instance of the SVG that is rendered. This means that if the first instance is hidden or removed from the DOM, all other references will break. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* When using 'sprite' mode, it is important to note that the symbol definition will be included with the first instance of the SVG that is rendered. This means that if the first instance is hidden or removed from the DOM, all other references will break. | |
* When using 'sprite' mode, it is important to note that the symbol definition will be included with the first instance of the SVG that is rendered. This means that if the first instance is hidden or removed from the DOM, all other references will break. |
Question: is this unique to Astro's implementation, or would this be something common to using a sprite sheet in general? I get that it's a caveat, but if it's not an Astro-specific caveat then it's a little bit more for the reader to have to process before they even see how to use this feature.
I might suggest we either remove this paragraph entirely (we do say full details in the RFC!), or place it after the code example if this is a super key thing and people will have a lot of trouble using the feature if they don't know this immediately up front.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unique to the implementation that we chose to go with. Though it would probably apply to the spritesheet in general but is significantly less common if you just have a single spritesheet.
This is something I was debating about adding as an enhancement later or opt-in. Would need some help from the core team on it though.
I'll leave it up to you if we keep this caveat or remove it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, for this stage of the game, I say we omit the caveat in these docs here. We just get people up and running quickly and happily, and they can check the RFC when and if they run into trouble!
Co-authored-by: Sarah Rainsberger <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approving for docs!
(Also noting, as I always do, that I don't see any new errors added with this feature. If that changes before the feature is released, then please loop me back in! 🙌 )
Co-authored-by: Sarah Rainsberger <[email protected]>
Thank you all for the help to get this in! I greatly appreciate all the reviews and feedback! |
Seems there is some kind of issue with the refactor of |
Initial fix: #12511 Going to do some testing and ensure I add a test to fail on previous case. Tested with |
Changes
This PR implements Built-in SVG Components following up on the discussions on withastro/roadmap#699
By default, SVG files will be components and can be imported by referencing their file path and then treating them like a component. Take this for example:
previously:
now:
GOALS:
<svg>
element while overriding the existing props<svg>
on a page can hurt browser parsing performance. We can automatically reduce the number of nodes by using<symbol>
and<use>
.<svg>
often have xmlns and xmlns:xlink and version attributes. These are not needed in HTML5 and we can automatically drop them to save a few bytes.<Icon>
component for each supported framework, we’ll make it easy for users to avoid this anti-pattern. It’s so common now because there aren’t many other good solutions.This builds a foundation for us which can be extended in the future within the core astro and even by third-parties. For instance using
astro-icon
, which has been the critical plugin when it comes to icons, as a playground, I was able to extend the foundation built here to add dynamic icon import for Iconify.FUTURE:
/cc @natemoo-re
Testing
I have ensured that existing tests are functioning properly and added additional suite for SVG Components.
Manually, I have updated the blog example to include using the SVG components. I have also tested locally against
astro-icon
and its demo application. Will work on some testing for this change.Docs
Docs added in withastro/docs#9911