withastro / roadmap

Ideas, suggestions, and formal RFC proposals for the Astro project.
320 stars 30 forks source link

Container API: render components in isolation #533

Closed Princesseuh closed 6 months ago

Princesseuh commented 1 year ago

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

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

https://github.com/withastro/roadmap/discussions/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!

tansanDOTeth commented 1 year ago

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 commented 1 year ago

Bumping for storybook support.

RodrigoTomeES commented 1 year ago

Hi!, any update about this? It would be amazing to have Astro in Storybook

Princesseuh commented 1 year ago

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

jsve commented 1 year ago

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:

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 commented 1 year ago

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 commented 1 year ago

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.

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 commented 1 year ago

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 commented 1 year ago

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 commented 12 months ago

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 commented 12 months ago

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

lschierer commented 11 months ago

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 commented 11 months ago

@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.

RATIU5 commented 7 months ago

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 commented 7 months ago

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 commented 7 months ago

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 commented 7 months ago

@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 commented 7 months ago

@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 commented 7 months ago

@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 commented 7 months ago

@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 commented 7 months ago

@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 commented 7 months ago

@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 commented 6 months ago

RFC Stage 3 is now open: https://github.com/withastro/roadmap/issues/533

ematipico commented 6 months ago

@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 commented 6 months ago

Stage 3 RFC available in #916