diff --git a/.changeset/ninety-cups-decide.md b/.changeset/ninety-cups-decide.md new file mode 100644 index 0000000000..3a637fc866 --- /dev/null +++ b/.changeset/ninety-cups-decide.md @@ -0,0 +1,22 @@ +--- +'astro': patch +--- + +Adds a new function called `insertPageRoute` to the Astro Container API. + +The new function is useful when testing routes that, for some business logic, use `Astro.rewrite`. + +For example, if you have a route `/blog/post` and for some business decision there's a rewrite to `/generic-error`, the container API implementation will look like this: + +```js +import Post from "../src/pages/Post.astro"; +import GenericError from "../src/pages/GenericError.astro"; +import { experimental_AstroContainer as AstroContainer } from "astro/container"; + +const container = await AstroContainer.create(); +container.insertPageRoute("/generic-error", GenericError); +const result = await container.renderToString(Post); +console.log(result) // this should print the response from GenericError.astro +``` + +This new method only works for page routes, which means that endpoints aren't supported. diff --git a/packages/astro/e2e/fixtures/actions-blog/src/components/PostComment.tsx b/packages/astro/e2e/fixtures/actions-blog/src/components/PostComment.tsx index 4d93e310d7..4c763b2cab 100644 --- a/packages/astro/e2e/fixtures/actions-blog/src/components/PostComment.tsx +++ b/packages/astro/e2e/fixtures/actions-blog/src/components/PostComment.tsx @@ -18,7 +18,7 @@ export function PostComment({
{ e.preventDefault(); const form = e.target as HTMLFormElement; diff --git a/packages/astro/src/container/index.ts b/packages/astro/src/container/index.ts index 3943ad57b0..777a5deb20 100644 --- a/packages/astro/src/container/index.ts +++ b/packages/astro/src/container/index.ts @@ -539,6 +539,30 @@ export class experimental_AstroContainer { return renderContext.render(componentInstance, slots); } + /** + * It stores an Astro **page** route. The first argument, `route`, gets associated to the `component`. + * + * This function can be useful when you want to render a route via `AstroContainer.renderToString`, where that + * route eventually renders another route via `Astro.rewrite`. + * + * @param {string} route - The URL that will render the component. + * @param {AstroComponentFactory} component - The component factory to be used for rendering the route. + * @param {Record} params - An object containing key-value pairs of route parameters. + */ + public insertPageRoute(route: string,component: AstroComponentFactory, params?: Record) { + const url = new URL(route, 'https://example.com/'); + const routeData: RouteData = this.#createRoute(url, params ?? {}, 'page'); + this.#pipeline.manifest.routes.push({ + routeData, + file: '', + links: [], + styles: [], + scripts: [], + }); + const componentInstance = this.#wrapComponent(component, params); + this.#pipeline.insertRoute(routeData, componentInstance); + } + #createRoute(url: URL, params: Record, type: RouteType): RouteData { const segments = removeLeadingForwardSlash(url.pathname) .split(posix.sep) diff --git a/packages/astro/src/container/pipeline.ts b/packages/astro/src/container/pipeline.ts index 1547130284..6e927c2e5b 100644 --- a/packages/astro/src/container/pipeline.ts +++ b/packages/astro/src/container/pipeline.ts @@ -89,6 +89,11 @@ export class ContainerPipeline extends Pipeline { } // At the moment it's not used by the container via any public API - // @ts-expect-error It needs to be implemented. - async getComponentByRoute(_routeData: RouteData): Promise {} + async getComponentByRoute(routeData: RouteData): Promise { + const page = this.#componentsInterner.get(routeData); + if (page) { + return page.page(); + } + throw new Error("Couldn't find component for route " + routeData.pathname); + } } diff --git a/packages/astro/test/container.test.js b/packages/astro/test/container.test.js index 13138ef951..3a46b610dd 100644 --- a/packages/astro/test/container.test.js +++ b/packages/astro/test/container.test.js @@ -11,6 +11,7 @@ import { renderHead, renderSlot, renderTemplate, + createAstro } from '../dist/runtime/server/index.js'; import testAdapter from './test-adapter.js'; import { loadFixture } from './test-utils.js'; @@ -59,6 +60,43 @@ describe('Container', () => { assert.match(response, /hello world/); }); + it('Renders a div with hello world text', async () => { + const $$Astro = createAstro(); + const Page = createComponent((result, props, slots) => { + const Astro = result.createAstro($$Astro, props, slots); + return Astro.rewrite('/example') + }); + + const Example = createComponent((result) => { + return render`${renderComponent( + result, + 'BaseLayout', + BaseLayout, + {}, + { + default: () => render`${maybeRenderHead(result)}
hello world
`, + head: () => render` + ${renderComponent( + result, + 'Fragment', + Fragment, + { slot: 'head' }, + { + default: () => render``, + }, + )} + `, + }, + )}`; + }); + + const container = await experimental_AstroContainer.create(); + container.insertPageRoute('/example', Example); + const response = await container.renderToString(Page); + + assert.match(response, /hello world/); + }); + it('Renders a slot', async () => { const Page = createComponent( (result, _props, slots) => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5d7755a41..6ad282b8e1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2514,6 +2514,12 @@ importers: specifier: workspace:* version: link:../../.. + packages/astro/test/fixtures/container: + dependencies: + astro: + specifier: workspace:* + version: link:../../.. + packages/astro/test/fixtures/container-custom-renderers: dependencies: '@astrojs/react':