0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-02-17 22:44:24 -05:00

fix(container): emit components as partials (#12239)

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
Emanuele Stoppa 2024-10-16 14:07:59 +01:00 committed by GitHub
parent a3d30a602a
commit 2b6daa5840
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 60 additions and 2 deletions

View file

@ -0,0 +1,22 @@
---
"astro": patch
---
**BREAKING CHANGE to the experimental Container API only**
Changes the default page rendering behavior of Astro components in containers, and adds a new option `partial: false` to render full Astro pages as before.
Previously, the Container API was rendering all Astro components as if they were full Astro pages containing `<!DOCTYPE html>` by default. This was not intended, and now by default, all components will render as [page partials](https://docs.astro.build/en/basics/astro-pages/#page-partials): only the contents of the components without a page shell.
To render the component as a full-fledged Astro page, pass a new option called `partial: false` to `renderToString()` and `renderToResponse()`:
```js
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import Card from "../src/components/Card.astro";
const container = AstroContainer.create();
await container.renderToString(Card); // the string will not contain `<!DOCTYPE html>`
await container.renderToString(Card, { partial: false }); // the string will contain `<!DOCTYPE html>`
```

View file

@ -89,6 +89,14 @@ export type ContainerRenderOptions = {
* ```
*/
props?: Props;
/**
* When `false`, it forces to render the component as it was a full-fledged page.
*
* By default, the container API render components as [partials](https://docs.astro.build/en/basics/astro-pages/#page-partials).
*
*/
partial?: boolean;
};
export type AddServerRenderer =
@ -487,6 +495,7 @@ export class experimental_AstroContainer {
request,
pathname: url.pathname,
locals: options?.locals ?? {},
partial: options?.partial ?? true,
});
if (options.params) {
renderContext.params = options.params;

View file

@ -56,6 +56,7 @@ export class RenderContext {
public params = getParams(routeData, pathname),
protected url = new URL(request.url),
public props: Props = {},
public partial: undefined | boolean = undefined,
) {}
/**
@ -76,9 +77,10 @@ export class RenderContext {
routeData,
status = 200,
props,
partial = undefined,
}: Pick<RenderContext, 'pathname' | 'pipeline' | 'request' | 'routeData'> &
Partial<
Pick<RenderContext, 'locals' | 'middleware' | 'status' | 'props'>
Pick<RenderContext, 'locals' | 'middleware' | 'status' | 'props' | 'partial'>
>): Promise<RenderContext> {
const pipelineMiddleware = await pipeline.getMiddleware();
return new RenderContext(
@ -93,6 +95,7 @@ export class RenderContext {
undefined,
undefined,
props,
partial,
);
}
@ -319,7 +322,7 @@ export class RenderContext {
const componentMetadata =
(await pipeline.componentMetadata(routeData)) ?? manifest.componentMetadata;
const headers = new Headers({ 'Content-Type': 'text/html' });
const partial = Boolean(mod.partial);
const partial = typeof this.partial === 'boolean' ? this.partial : Boolean(mod.partial);
const response = {
status,
statusText: 'OK',

View file

@ -252,6 +252,16 @@ describe('Container with renderers', () => {
const html = await response.text();
assert.match(html, /I am a react button/);
assert.doesNotMatch(html, /<!DOCTYPE html>/);
});
it('the endpoint should return the HTML of the React component, with DOCTYPE when rendered when partial is off', async () => {
const request = new Request('https://example.com/react-as-page');
const response = await app.render(request);
const html = await response.text();
assert.match(html, /I am a react button/);
assert.match(html, /<!DOCTYPE html>/);
});
it('the endpoint should return the HTML of the Vue component', async () => {
@ -260,6 +270,7 @@ describe('Container with renderers', () => {
const html = await response.text();
assert.match(html, /I am a vue button/);
assert.doesNotMatch(html, /<!DOCTYPE html>/);
});
it('Should render a component with directives', async () => {
@ -269,5 +280,6 @@ describe('Container with renderers', () => {
assert.match(html, /Button not rendered/);
assert.match(html, /I am a react button/);
assert.doesNotMatch(html, /<!DOCTYPE html>/);
});
});

View file

@ -0,0 +1,12 @@
import type {APIRoute} from "astro";
import { experimental_AstroContainer } from "astro/container";
import renderer from '@astrojs/react/server.js';
import Component from "../components/button.jsx"
export const GET: APIRoute = async (ctx) => {
const container = await experimental_AstroContainer.create();
container.addServerRenderer({ renderer });
return await container.renderToResponse(Component, {
partial: false
});
}