Skip to content
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

💡 RFC: Once component - to render content only once per request #958

Closed
1 of 3 tasks
matthewp opened this issue Jul 30, 2021 · 14 comments
Closed
1 of 3 tasks

💡 RFC: Once component - to render content only once per request #958

matthewp opened this issue Jul 30, 2021 · 14 comments

Comments

@matthewp
Copy link
Contributor

Background & Motivation

#370 goes over the desire to have scripts that only render once. This has come up quite a bit.

Another case is <svg> when paired with <use>, you only want the svg to render the first time.

Proposed Solution

Possible solutions

I'm proposing a low-level <Once> component that renders into children only once per request. This allows you to make any content render only once.

Later we can add things like <Script> or <Link> that are merely wrappers around this Once component.

Usage:

<Once>
  <script>
    {`console.log('this will only log the once!`}
  </script>
</Once>

Alternatives considered

  1. Just automatically only rendering <script>s. There are cases where you want the script to render more than once, such as when it's placed next to content it needs to mess with (like embeds). We could have made this optional but it might be confusing.
  2. Jumping straight to a <Script> but since this is needed for other types of content it makes sense to start with a Once.

Risks, downsides, and/or tradeoffs

None

Open Questions

  • Are people happy with the name Once.
  • Should this be a global or imported from astro/components? (I think the latter)

Detailed Design

No response

Help make it happen!

  • I am willing to submit a PR to implement this change.
  • I am willing to submit a PR to implement this change, but would need some guidance.
  • I am not willing to submit a PR to implement this change.
@jasikpark
Copy link
Contributor

Isn't this just what <script type="module" does? I don't fully understand the usecase of things like<Script/>

@FredKSchott
Copy link
Member

The use-case that we always use is: imagine that you had 50 components in a list. If there was a script tag in the template for every component, you would end up with 50 duplicate script tags on the page.

@matthewp I like it! Two questions that pops up:

  1. where do these components get rendered to the page?
  2. is there any use-case for anything other than a script or info here?

I couldn't help but think while reading this: "this feels a lot like <svelte:head>". Would we get more out of building <once> with the behavior of: "render this thing to the head, one time" That feels much more explicit, and I think it's relevant that the svelte team went that direction, receive generally positive feedback on it (afaik), and then never later needed anything else (afaik, again :).

@jasikpark
Copy link
Contributor

Oh! So it's more like C++ "pragma once" in that it's signaling to the compiler that only one instance of this should exist in compiled out code

@matthewp
Copy link
Contributor Author

@jasikpark Yep, this is exactly like pragma once!

@matthewp
Copy link
Contributor Author

@FredKSchott These get rendered in the same place they are authored. I'd recommend a Portal RFC to move things around as a first step. But that seems like a non-trivial compiler change to me.

The other primary use case is . You define your svg once and then have use statements whenever it is used elsewhere.

@jonathantneal
Copy link
Contributor

jonathantneal commented Aug 3, 2021

Bouncing off @aFuzzyBear, I was wondering if additional special attributes could be registered to emulate this behavior?

<script once:last src="ref" />

<script once:last src="ref" />

<script once:last src="ref" />
<svg once:first><symbol id="ref-1" /><symbol id="ref-2" /></svg>
<svg><use href="#ref-1"></svg>

<svg once:first><symbol id="ref-1" /><symbol id="ref-2" /></svg>
<svg><use href="#ref-2"></svg>

@aFuzzyBear
Copy link
Contributor

I was wondering if this could be used as a client:only:once with the :once used as an adverb the <MyComponent client:load:once/>

@jonathantneal
Copy link
Contributor

@aFuzzyBear, forgive a potentially daft question, but are there client:* attributes that generate non-JS transformations? That would be my only hesitation to that terminology, if I were trying to reason about how it worked.

@FredKSchott
Copy link
Member

RFC Call: Consensus reached that this is something that we'd like to see added. No decision made on syntax, to be decided in this thread or a later RFC call if needed.

@drwpow
Copy link
Member

drwpow commented Aug 3, 2021

This seems like a way to control “setup“ for an Astro component (<script only once>), or maybe even “assets” (in the SVG <use> case, or CSS), offering really good DX of Astro components to pull in their deps with minimal fuss.

I wonder if the “once” framing to be a bit too imperative? I.e. what if an Astro Component appears within <Once>—does that mean it’s restricted from ever rendering anywhere else on the page? How do nested Astro components with <Once> work? The implications are less clear. But if an Astro Component is marked as “setup” or ”asset” instead, then that seems more permissive that it can be used elsewhere; it’s only within this parent tree that setup code / deps aren’t loaded over and over again (i.e. more markup can be duplicated than “once” may suggest).

I think as some have pointed out with portals, we may also want some hooks into where things render. In the case of <script>, should it render before the first instance? Or at the end of the very last? Or at the beginning or end of of <body>?

All that saying, maybe the framing of “once” is too prescriptive and maybe there’s an alternate framing that solves the original usecase. Or we separate <script> setups from loading deps such as CSS / SVG?

@matthewp
Copy link
Contributor Author

matthewp commented Aug 3, 2021

Here's how you might make svgs easier in your application with svg use.

First define your SVG in an Astro component.

Circle.astro

---
import { Once } from 'astro/components';
---
<Once>
  <svg viewBox="0 0 30 10" xmlns="http://www.w3.org/2000/svg">
    <circle id="myCircle" cx="5" cy="5" r="4" stroke="blue"/>
  </svg>
</Once>

<use href="#myCircle" x="10" fill="blue"/>

Then use this component as many times as you want.

<Circle />
<Circle />
<Circle />
<Circle />
<Circle />

Will render to:

<svg viewBox="0 0 30 10" xmlns="http://www.w3.org/2000/svg">
  <circle id="myCircle" cx="5" cy="5" r="4" stroke="blue"/>
</svg>

<use href="#myCircle" x="10" fill="blue"/>
<use href="#myCircle" x="10" fill="blue"/>
<use href="#myCircle" x="10" fill="blue"/>
<use href="#myCircle" x="10" fill="blue"/>
<use href="#myCircle" x="10" fill="blue"/>

@drwpow drwpow moved this from Needs Discussion to Needs Work in 💡 RFC Tracker [No Longer Used] Aug 17, 2021
@matthewp
Copy link
Contributor Author

I'm dropping this RFC as I think a lower-level API is needed first. There's currently not a great way to even know, inside of a component, how many times its been rendered. Rather than a high-level attribute/component (which still might be a nice convenience), I'd rather first see a way to know some metadata bout rendering. Maybe something like:

---
const renders = Astro.metadata.renderCount;
---

{renders === 0 ? <div>I'm rendering...</div> : ''}

But this would be a new RFC. Probably I'd want to see if there were similar low-level use-cases that could use this sort of functionality (or other kinds of metadata).

💡 RFC Tracker [No Longer Used] automation moved this from Needs Work to Completed Aug 23, 2021
@jasikpark
Copy link
Contributor

Here's how you might make svgs easier in your application with svg use.

First define your SVG in an Astro component.

Circle.astro

---
import { Once } from 'astro/components';
---
<Once>
  <svg viewBox="0 0 30 10" xmlns="http://www.w3.org/2000/svg">
    <circle id="myCircle" cx="5" cy="5" r="4" stroke="blue"/>
  </svg>
</Once>

<use href="#myCircle" x="10" fill="blue"/>

Then use this component as many times as you want.

<Circle />
<Circle />
<Circle />
<Circle />
<Circle />

Will render to:

<svg viewBox="0 0 30 10" xmlns="http://www.w3.org/2000/svg">
  <circle id="myCircle" cx="5" cy="5" r="4" stroke="blue"/>
</svg>

<use href="#myCircle" x="10" fill="blue"/>
<use href="#myCircle" x="10" fill="blue"/>
<use href="#myCircle" x="10" fill="blue"/>
<use href="#myCircle" x="10" fill="blue"/>
<use href="#myCircle" x="10" fill="blue"/>

Slightly cursed, but what if someone wrote an "SVG Renderer" where the actual svg source is the library, and each usage of the svg in code is translated to <use> xD. I don't think that actually makes sense, but it sure would be interesting.

@matthewp
Copy link
Contributor Author

I think even a renderer would not be able to implement this behavior without some help from Astro. I like #706 as a lower-level solution to this problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
Development

No branches or pull requests

6 participants