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

Container API: render components in isolation #533

Closed
Princesseuh opened this issue Mar 23, 2023 · 29 comments
Closed

Container API: render components in isolation #533

Princesseuh opened this issue Mar 23, 2023 · 29 comments

Comments

@Princesseuh
Copy link
Member

Body

Summary

Astro components are tightly coupled to astro (the metaframework). This proposal introduces a possible server-side API for rendering .astro files in isolation, outside of the full astro build pipeline.

Background & Motivation

Some of our own proposals, as well as many third-party tool integrations, are blocked until we expose a proper server-side rendering API for .astro components. Other frameworks have tools like preact-render-to-string or react-dom/server that make rendering components on the server straight-forward.

We've avoided doing this because... it's very hard! Part of the appeal of .astro components is that they have immediate access to a number of powerful APIs, context about your site, and a rich set of framework renderers. In the full astro build process, we are able to wire up all this context invisibly. Wiring up the context manually as a third-party is prohibitively complex.

Goals

  • Provide an API for rendering .astro components in isolation
  • Expose a familiar, user-friendly API
  • Surface enough control for full astro parity, but abstract away internal APIs
  • Enable third-party tools to consume .astro files on the server
  • Enable unit testing of .astro component output
  • Possibly unblock .mdx compiledContent/html output?
  • Support Astro framework renderers (@astrojs/*) if possible

Example

import { AstroContainer } from 'astro/container';
import Component from './components/Component.astro';

const astro = new AstroContainer();
console.log(await astro.renderToString(Component, { props, slots }))

The container can be optionally constructed with some settings that are typically reserved for Astro configuration.

const astro = new AstroContainer({
  mode?: 'development' | 'production';
  site?: string;
  renderers?: Renderer[]
});

The second argument to renderToString optionally provides render-specific data that may be exposed through the Astro global, like props, slots, request, and/or params.

await astro.renderToString(Component, { props, slots, request, params })
@natemoo-re
Copy link
Member

Major point to be resolved: how do we surface styles and scripts? Ideally this API would work similarly to Svelte's Component.render(), but Astro's compiler strips out scripts and styles to be handled by Vite. I have yet to figure out what we should do here!

@JReinhold
Copy link

FWIW, even though it's out of scope here (as it should be), based on the API described above, this could work in Storybook's client-side environment too, similar to how preact-render-to-string or react-dom/server does. In the end it would depend on which API's it would use under the hood, like node:fs, etc.

@zellwk
Copy link

zellwk commented Apr 6, 2023

I'm really eager for this one right now. I'm unsure of other use cases, but this seems like it'll make it easy-peasy to render MDX blog posts into RSS!

@toinbis
Copy link

toinbis commented Apr 11, 2023

#462 - initial discussion mention vite-plugin-astro as a requirement. Is that true? Would that mean that express.js or https://hono.dev would not be able to generate components using this API?

Thanks a lot for shipping this anyways!

kogakure added a commit to kogakure/website-astro-stefanimhoff.de that referenced this issue Jun 13, 2023
Astro is currently not able to render the compiled HTML of MDX files to a string.
This makes it impossible to render the content for an RSS feed.

Issue: Container API: render components in isolation
withastro/roadmap#533

Proposal: MDX compiledContent() support
withastro/roadmap#419

To still be able to have full content for RSS feeds, this dirty hack,
Scott Willsey writes about in his 2-part blog post is needed:

https://scottwillsey.com/rss-pt1/
https://scottwillsey.com/rss-pt2/
kogakure added a commit to kogakure/website-astro-stefanimhoff.de that referenced this issue Jun 13, 2023
Astro is currently not able to render the compiled HTML of MDX files to a string.
This makes it impossible to render the content for an RSS feed.

Issue: Container API: render components in isolation
withastro/roadmap#533

Proposal: MDX compiledContent() support
withastro/roadmap#419

To still be able to have full content for RSS feeds, this dirty hack,
Scott Willsey writes about in his 2-part blog post is needed:

https://scottwillsey.com/rss-pt1/
https://scottwillsey.com/rss-pt2/
@tansanDOTeth
Copy link

This would be really helpful for my use case where I am trying to create unique SVGs.

// Outputs: /svg-1.json
export async function get({params, request, props}) {
  return {
    body: await astro.renderToString(SvgComponent, { props, slots, request, params })
  };
}

@gvkhna

This comment has been minimized.

@RodrigoTomeES

This comment was marked as spam.

@Princesseuh
Copy link
Member Author

If we had any updates, we'd share them. Please refrain from posting unnecessary comments.

@jsve
Copy link

jsve commented Oct 30, 2023

I found this thread from the storybook discussion. I think that's probably one of the more apparent use cases. I'd like to add one of my own:

In recent projects we have established a pattern to allow a parent component to evaluate if it's children will output anything, and take appropriate actions if not. To achieve this, each component exposes a shouldRender function which the parent can call. At the moment, those functions do not render anything, they just return a boolean. For example if typeof x !== undefined && y.length > 0.

Two examples of where we could use this:

  • a grid that is defined with 3 items in the CMS, but only 2 have enough information to render something meaningful => render with 2 grid-columns
  • a page defined in the CMS, but it has no content => render nothing, i.e. don't occupy the url with an empty page.

In some scenarios this pattern becomes difficult to set up and maintain. It probably adds some overhead that we can not optimise. Being able to render children in isolation, like proposed in this RFC, would greatly simplify our workflow! Hopefully without any additional overhead at all :)

@smnbbrv
Copy link

smnbbrv commented Oct 30, 2023

Another use-case is as simple as ability to cache the rendered HTML.

If you have static content that comes either from some CMS or is changed once a day or similar it does not make sense to cache the underlying data if you can directly cache HTML and put it directly in the response instead of rendering it every single time.

@kizu
Copy link

kizu commented Nov 11, 2023

I was mostly working around this issue by using an external markdown renderer, but recently stumbled upon the problem with the images — given Astro handles them in a “smart” way, the generated images are not available in the RSS that I generate, so it would be really nice to see this issue to move forward.

Major point to be resolved: how do we surface styles and scripts? Ideally this API would work similarly to Svelte's Component.render(), but Astro's compiler strips out scripts and styles to be handled by Vite. I have yet to figure out what we should do here!

I think for a lot of use cases for this feature, it would be totally fine to omit the styles and scripts in the first implementation, and think about them later.

  • RSS does not require nor styles, not scripts. By providing additional props/config to the render, we could inline some crucial styles, but in general, we'd be happy with just HTML.
  • Rendering SVG or any other XML would also not require any scripts or styles.

Are there other blockers that prevent the development of this? If there is no obvious path for styles and scripts, but there is one for just outputting the rendered HTML — can we go with it to unlock this issue, and then work iteratively on the improvements for it?

@regisphilibert
Copy link

regisphilibert commented Nov 11, 2023

I agree that most use cases I can think of will not necessarily need any style/scripts and if needed can be added manually whenever the produce html string is eventually used.

I wouldn't mind it being developed without style/script in its first implementation.

Later on, scripts and styles could be added as different methods.

const element = {
  html: Component.render(),
  js: Component.scripts(),
  css: Component.styles(),
}

@cdtut
Copy link

cdtut commented Nov 20, 2023

await astro.renderToString(Component, { props, slots, request, params }) will be possible to use outside of astro so we can use only templating part of astro with any http framework like express or fastify?

@santi020k
Copy link

This could be really useful for testing rendering with astro, I don't know if it is possible in any other way, but I haven't found a way yet.

@gvkhna
Copy link

gvkhna commented Dec 5, 2023

Adding my usecase, besides storybook I want this for rendering screenshots/mockups directly to images.

@lschierer
Copy link

I'm curious what "in isolation" means in this context. To me it means sandboxing one component from another, but you seem to mean something more than that. I'm wondering if this would help me solve situations where reusing a component that stands up a nanostore causes the different instances of the component to stomp on each other's storage.

@tylermercer
Copy link

tylermercer commented Dec 12, 2023

@lschierer No, this is about having a way to render a component to HTML programmatically.

The issue you describe is not really within Astro's purview to solve—you can solve it using JS. For example, you could have each component receive a key prop that it uses to get the nanostore (by looking it up in a shared Map), with a sensible default value. Then two instances of the same component that should have different stores can use their differing keys to get them.

Or, if they never need to share state, just call atom separately for each component instance. E.g. get all the component root elements via querySelectorAll, and for each do some setup that includes creating the unique atom for that instance.

tgecho added a commit to tgecho/esimmler.com that referenced this issue Apr 10, 2024
…st summaries on the home page

But then I still wanted rendered content in the rss feed, and for that I *still* need to be able to render markdown to string, which is apparently stuck here: withastro/roadmap#533
@RATIU5
Copy link

RATIU5 commented Apr 25, 2024

Has progress halted on this? It doesn't seem like the branch is very active. I for one am very eager to use this API.

@Princesseuh
Copy link
Member Author

Has progress halted on this? It doesn't seem like the branch is very active. I for one am very eager to use this API.

@ematipico is currently working on this and shared a preview in our Discord yesterday.

@ematipico
Copy link
Member

ematipico commented Apr 26, 2024

An update: I am currently writing the RFC Stage 3, where we can jump in and discuss expectations and usage.

In the meantime, here's a branch with a working test and a screenshot of the usage. https://github.com/withastro/astro/compare/feat/container-api?expand=1

Screenshot_2024-04-25_at_17 01 04

@cdtut
Copy link

cdtut commented Apr 26, 2024

@ematipico Can we use .astro file instead of object in renderToString? So we can use astro instead of other templating languages like liquid and framework like express.

@sph-e
Copy link

sph-e commented Apr 26, 2024

@ematipico Can we use .astro file instead of object in renderToString? So we can use astro instead of other templating languages like liquid and framework like express.

I'd like to see this as well, at least as an option.

@natemoo-re
Copy link
Member

@ematipico Can we use .astro file instead of object in renderToString? So we can use astro instead of other templating languages like liquid and framework like express.

This example is using an internal API for prototyping purposes, it does not reflect the final API! In a normal Astro (Vite) environment, you'll be able to import .astro files without an issue. The ability to import .astro files in a vanilla Node environment is a completely different topic that we'd consider out of scope here.

@cdtut
Copy link

cdtut commented May 2, 2024

@natemoo-re Would it be so different to support it vanilla Node? How is it out of scope for rendering components in isolation that seems like exactly the case and people would like to use it that way.

@natemoo-re
Copy link
Member

@natemoo-re Would it be so different to support it vanilla Node? How is it out of scope for rendering components in isolation that seems like exactly the case and people would like to use it that way.

This use case will likely be addressed by something like Vite's recent runtime API. I'm just calling out the fact that importing and compiling .astro files outside of a Vite environment isn't something that's likely to be shipped along with this first iteration of the Container API. We know that's something people want, but we're focusing on use cases within existing Astro projects for now to reduce scope so we can ship something.

@smnbbrv
Copy link

smnbbrv commented May 3, 2024

@natemoo-re Would it make sense to create another issue where we can track the solution with e.g. runtime API ? I'm subscribed to this thread exclusively for this feature that @cdtut mentions, and seems like many others as well.

@ematipico
Copy link
Member

ematipico commented May 6, 2024

RFC Stage 3 is now open: #533

@ematipico ematipico reopened this May 6, 2024
@ematipico
Copy link
Member

@natemoo-re Would it make sense to create another issue where we can track the solution with e.g. runtime API ? I'm subscribed to this thread exclusively for this feature that @cdtut mentions, and seems like many others as well.

My advice is to open a stage 1 RFC after the container API lands in user-land and people start using it. I think it's best to first build up good foundations and then implement new things on top of it. Let's first shape the scope of the APIs together, and then let's start proposing new stuff. Maybe landing this API sooner rather than later can be a signal for Vite to stabilise their runtime APIs.

Now that the wheels started to turn, it wouldn't take long to see more APIs for the container APIs.

@florian-lefebvre
Copy link
Member

Stage 3 RFC available in #916

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Stage 3: Accepted Proposals, Has RFC
Development

No branches or pull requests