diff --git a/.changeset/soft-rabbits-draw.md b/.changeset/soft-rabbits-draw.md new file mode 100644 index 000000000000..87094d3e16bd --- /dev/null +++ b/.changeset/soft-rabbits-draw.md @@ -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 `` 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 `` +await container.renderToString(Card, { partial: false }); // the string will contain `` +``` + diff --git a/packages/astro/src/container/index.ts b/packages/astro/src/container/index.ts index 9aa4e2d2f2d9..374f0252ca61 100644 --- a/packages/astro/src/container/index.ts +++ b/packages/astro/src/container/index.ts @@ -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; diff --git a/packages/astro/src/core/render-context.ts b/packages/astro/src/core/render-context.ts index 952c9c03b2ea..02a0b46f6d0b 100644 --- a/packages/astro/src/core/render-context.ts +++ b/packages/astro/src/core/render-context.ts @@ -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 & Partial< - Pick + Pick >): Promise { 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', diff --git a/packages/astro/test/container.test.js b/packages/astro/test/container.test.js index 7d8b61d9c658..13138ef95188 100644 --- a/packages/astro/test/container.test.js +++ b/packages/astro/test/container.test.js @@ -252,6 +252,16 @@ describe('Container with renderers', () => { const html = await response.text(); assert.match(html, /I am a react button/); + assert.doesNotMatch(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, //); }); 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, //); }); 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, //); }); }); diff --git a/packages/astro/test/fixtures/container-custom-renderers/src/pages/react-as-page.ts b/packages/astro/test/fixtures/container-custom-renderers/src/pages/react-as-page.ts new file mode 100644 index 000000000000..5ddddadac3bb --- /dev/null +++ b/packages/astro/test/fixtures/container-custom-renderers/src/pages/react-as-page.ts @@ -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 + }); +}