diff --git a/.changeset/dull-carpets-breathe.md b/.changeset/dull-carpets-breathe.md index 63aca3cefd71..b009e5ccfd90 100644 --- a/.changeset/dull-carpets-breathe.md +++ b/.changeset/dull-carpets-breathe.md @@ -1,5 +1,11 @@ --- 'astro': patch +'@astrojs/preact': minor +'@astrojs/svelte': minor +'@astrojs/react': minor +'@astrojs/solid-js': minor +'@astrojs/lit': minor +'@astrojs/vue': minor --- Adds a new function called `addServerRenderer` to the Container API. Use this function to manually store renderers inside the instance of your container. @@ -14,10 +20,22 @@ import vueRenderer from '@astrojs/vue/server.js'; import ReactComponent from "../components/button.jsx" import VueComponent from "../components/button.vue" +// MDX runtime is contained inside the Astro +import mdxRenderer from "@astrojs/jsx/serverr.js" + +// In case you need to import a custom renderer +import customRenderer from "../renderers/custoRender.js"; + export const GET: APIRoute = async (ctx) => { const container = await experimental_AstroContainer.create(); - container.addServerRenderer("@astrojs/react", reactRenderer); - container.addServerRenderer("@astrojs/vue", vueRenderer); + container.addServerRenderer({ renderer: reactRenderer }); + container.addServerRenderer({ renderer: vueRenderer }); + container.addServerRenderer({ renderer: customRenderer }); + // You can pass a custom name too + container.addServerRenderer({ + name: "customRenderer", + renderer: customRenderer + }) const vueComponent = await container.renderToString(VueComponent) return await container.renderToResponse(Component); } diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 1fbb1c4dccec..e0cc4dcad72e 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -2977,7 +2977,12 @@ export interface AstroRenderer { jsxTransformOptions?: JSXTransformFn; } -export type SSRLoadedRendererValue = { +export interface NamedSSRLoadedRendererValue extends SSRLoadedRendererValue { + name: string; +} + +export interface SSRLoadedRendererValue { + name?: string; check: AsyncRendererComponentFn; renderToStaticMarkup: AsyncRendererComponentFn<{ html: string; diff --git a/packages/astro/src/container/index.ts b/packages/astro/src/container/index.ts index 452f97738091..97b922653cae 100644 --- a/packages/astro/src/container/index.ts +++ b/packages/astro/src/container/index.ts @@ -5,6 +5,7 @@ import type { ComponentInstance, ContainerImportRendererFn, MiddlewareHandler, + NamedSSRLoadedRendererValue, Props, RouteData, RouteType, @@ -84,6 +85,16 @@ export type ContainerRenderOptions = { props?: Props; }; +export type AddServerRenderer = + | { + renderer: NamedSSRLoadedRendererValue; + name: never; + } + | { + renderer: SSRLoadedRendererValue; + name: string; + }; + function createManifest( manifest?: AstroContainerManifest, renderers?: SSRLoadedRenderer[], @@ -279,28 +290,38 @@ export class experimental_AstroContainer { * ```js * import reactRenderer from "@astrojs/react/server.js"; * import vueRenderer from "@astrojs/vue/server.js"; + * import customRenderer from "../renderer/customRenderer.js"; * import { experimental_AstroContainer as AstroContainer } from "astro/container" * * const container = await AstroContainer.create(); - * container.addServerRenderer("@astrojs/react", reactRenderer); - * container.addServerRenderer("@astrojs/vue", vueRenderer); + * container.addServerRenderer(reactRenderer); + * container.addServerRenderer(vueRenderer); + * container.addServerRenderer("customRenderer", customRenderer); * ``` * - * @param name The name of the renderer. The name **isn't** arbitrary, and it should match the name of the package. - * @param renderer The server renderer exported by integration. + * @param options {object} + * @param options.name The name of the renderer. The name **isn't** arbitrary, and it should match the name of the package. + * @param options.renderer The server renderer exported by integration. */ - public addServerRenderer(name: string, renderer: SSRLoadedRendererValue) { + public addServerRenderer(options: AddServerRenderer): void { + const { renderer, name } = options; if (!renderer.check || !renderer.renderToStaticMarkup) { throw new Error( "The renderer you passed isn't valid. A renderer is usually an object that exposes the `check` and `renderToStaticMarkup` functions.\n" + "Usually, the renderer is exported by a /server.js entrypoint e.g. `import renderer from '@astrojs/react/server.js'`" ); } - - this.#pipeline.manifest.renderers.push({ - name, - ssr: renderer, - }); + if (isNamedRenderer(renderer)) { + this.#pipeline.manifest.renderers.push({ + name: renderer.name, + ssr: renderer, + }); + } else { + this.#pipeline.manifest.renderers.push({ + name, + ssr: renderer, + }); + } } // NOTE: we keep this private via TS instead via `#` so it's still available on the surface, so we can play with it. @@ -473,3 +494,7 @@ export class experimental_AstroContainer { return { default: componentFactory }; } } + +function isNamedRenderer(renderer: any): renderer is NamedSSRLoadedRendererValue { + return !!renderer?.name; +} diff --git a/packages/astro/src/jsx/server.ts b/packages/astro/src/jsx/server.ts index be55b0194b21..74857c490e66 100644 --- a/packages/astro/src/jsx/server.ts +++ b/packages/astro/src/jsx/server.ts @@ -1,6 +1,7 @@ import { AstroError, AstroUserError } from '../core/errors/errors.js'; import { AstroJSX, jsx } from '../jsx-runtime/index.js'; import { renderJSX } from '../runtime/server/jsx.js'; +import type { NamedSSRLoadedRendererValue } from '../@types/astro.js'; const slotName = (str: string) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase()); @@ -64,7 +65,10 @@ function throwEnhancedErrorIfMdxComponent(error: Error, Component: any) { } } -export default { +const renderer: NamedSSRLoadedRendererValue = { + name: 'astro:jsx', check, renderToStaticMarkup, }; + +export default renderer; diff --git a/packages/astro/test/container.test.js b/packages/astro/test/container.test.js index dd17cc565fd4..72d233ce4752 100644 --- a/packages/astro/test/container.test.js +++ b/packages/astro/test/container.test.js @@ -238,7 +238,7 @@ describe('Container with renderers', () => { let app; before(async () => { fixture = await loadFixture({ - root: new URL('./fixtures/container-react/', import.meta.url), + root: new URL('./fixtures/container-custom-renderers/', import.meta.url), output: 'server', adapter: testAdapter(), }); @@ -247,10 +247,18 @@ describe('Container with renderers', () => { }); it('the endpoint should return the HTML of the React component', async () => { - const request = new Request('https://example.com/api'); + const request = new Request('https://example.com/react'); const response = await app.render(request); const html = await response.text(); assert.match(html, /I am a react button/); }); + + it('the endpoint should return the HTML of the Vue component', async () => { + const request = new Request('https://example.com/vue'); + const response = await app.render(request); + const html = await response.text(); + + assert.match(html, /I am a vue button/); + }); }); diff --git a/packages/astro/test/fixtures/container-react/astro.config.mjs b/packages/astro/test/fixtures/container-custom-renderers/astro.config.mjs similarity index 69% rename from packages/astro/test/fixtures/container-react/astro.config.mjs rename to packages/astro/test/fixtures/container-custom-renderers/astro.config.mjs index e7ce274c003a..d85e1d6f4198 100644 --- a/packages/astro/test/fixtures/container-react/astro.config.mjs +++ b/packages/astro/test/fixtures/container-custom-renderers/astro.config.mjs @@ -1,7 +1,8 @@ import react from '@astrojs/react'; +import vue from '@astrojs/vue'; import { defineConfig } from 'astro/config'; // https://astro.build/config export default defineConfig({ - integrations: [react()], + integrations: [react(), vue()], }); diff --git a/packages/astro/test/fixtures/container-react/package.json b/packages/astro/test/fixtures/container-custom-renderers/package.json similarity index 71% rename from packages/astro/test/fixtures/container-react/package.json rename to packages/astro/test/fixtures/container-custom-renderers/package.json index 43d164ce8028..0824a724cc6c 100644 --- a/packages/astro/test/fixtures/container-react/package.json +++ b/packages/astro/test/fixtures/container-custom-renderers/package.json @@ -5,8 +5,10 @@ "type": "module", "dependencies": { "@astrojs/react": "workspace:*", + "@astrojs/vue": "workspace:*", "astro": "workspace:*", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "vue": "^3.4.27" } } diff --git a/packages/astro/test/fixtures/container-react/src/components/button.jsx b/packages/astro/test/fixtures/container-custom-renderers/src/components/button.jsx similarity index 100% rename from packages/astro/test/fixtures/container-react/src/components/button.jsx rename to packages/astro/test/fixtures/container-custom-renderers/src/components/button.jsx diff --git a/packages/astro/test/fixtures/container-custom-renderers/src/components/button.vue b/packages/astro/test/fixtures/container-custom-renderers/src/components/button.vue new file mode 100644 index 000000000000..5a55a61d982a --- /dev/null +++ b/packages/astro/test/fixtures/container-custom-renderers/src/components/button.vue @@ -0,0 +1,3 @@ + diff --git a/packages/astro/test/fixtures/container-react/src/pages/api.ts b/packages/astro/test/fixtures/container-custom-renderers/src/pages/react.ts similarity index 76% rename from packages/astro/test/fixtures/container-react/src/pages/api.ts rename to packages/astro/test/fixtures/container-custom-renderers/src/pages/react.ts index 7fae87247caf..d77eaead11e0 100644 --- a/packages/astro/test/fixtures/container-react/src/pages/api.ts +++ b/packages/astro/test/fixtures/container-custom-renderers/src/pages/react.ts @@ -1,10 +1,10 @@ import type {APIRoute, SSRLoadedRenderer} from "astro"; import { experimental_AstroContainer } from "astro/container"; -import server from '@astrojs/react/server.js'; +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("@astrojs/react", server); + container.addServerRenderer({ renderer }); return await container.renderToResponse(Component); } diff --git a/packages/astro/test/fixtures/container-custom-renderers/src/pages/vue.ts b/packages/astro/test/fixtures/container-custom-renderers/src/pages/vue.ts new file mode 100644 index 000000000000..9ea75d802297 --- /dev/null +++ b/packages/astro/test/fixtures/container-custom-renderers/src/pages/vue.ts @@ -0,0 +1,10 @@ +import type {APIRoute, SSRLoadedRenderer} from "astro"; +import { experimental_AstroContainer } from "astro/container"; +import renderer from '@astrojs/vue/server.js'; +import Component from "../components/button.vue" + +export const GET: APIRoute = async (ctx) => { + const container = await experimental_AstroContainer.create(); + container.addServerRenderer({ renderer }); + return await container.renderToResponse(Component); +} diff --git a/packages/integrations/lit/package.json b/packages/integrations/lit/package.json index 7bf9d81e1db2..947cb67fa7c7 100644 --- a/packages/integrations/lit/package.json +++ b/packages/integrations/lit/package.json @@ -21,7 +21,10 @@ "homepage": "https://docs.astro.build/en/guides/integrations-guide/lit/", "exports": { ".": "./dist/index.js", - "./server.js": "./server.js", + "./server.js": { + "default": "./server.js", + "types": "./server.d.ts" + }, "./client-shim.js": "./client-shim.js", "./dist/client.js": "./dist/client.js", "./hydration-support.js": "./hydration-support.js", @@ -33,6 +36,7 @@ "client-shim.min.js", "hydration-support.js", "server.js", + "server.d.ts", "server-shim.js" ], "scripts": { diff --git a/packages/integrations/lit/server.d.ts b/packages/integrations/lit/server.d.ts new file mode 100644 index 000000000000..bb2f29556c14 --- /dev/null +++ b/packages/integrations/lit/server.d.ts @@ -0,0 +1,2 @@ +import type { NamedSSRLoadedRendererValue } from 'astro'; +export default NamedSSRLoadedRendererValue; diff --git a/packages/integrations/lit/server.js b/packages/integrations/lit/server.js index e3f1c0340fe2..e9873a051667 100644 --- a/packages/integrations/lit/server.js +++ b/packages/integrations/lit/server.js @@ -112,6 +112,7 @@ async function renderToStaticMarkup(Component, props, slots) { } export default { + name: '@astrojs/lit', check, renderToStaticMarkup, }; diff --git a/packages/integrations/preact/src/server.ts b/packages/integrations/preact/src/server.ts index c502e68db240..c10c01c0e8fc 100644 --- a/packages/integrations/preact/src/server.ts +++ b/packages/integrations/preact/src/server.ts @@ -1,4 +1,4 @@ -import type { AstroComponentMetadata } from 'astro'; +import type { AstroComponentMetadata, NamedSSRLoadedRendererValue } from 'astro'; import { Component as BaseComponent, type VNode, h } from 'preact'; import { render } from 'preact-render-to-string'; import prepass from 'preact-ssr-prepass'; @@ -147,8 +147,11 @@ function filteredConsoleError(msg: string, ...rest: any[]) { originalConsoleError(msg, ...rest); } -export default { +const renderer: NamedSSRLoadedRendererValue = { + name: '@astrojs/preact', check, renderToStaticMarkup, supportsAstroStaticSlot: true, }; + +export default renderer; diff --git a/packages/integrations/react/package.json b/packages/integrations/react/package.json index 5ea15729bf7b..d352107105aa 100644 --- a/packages/integrations/react/package.json +++ b/packages/integrations/react/package.json @@ -24,8 +24,14 @@ "./actions": "./dist/actions.js", "./client.js": "./client.js", "./client-v17.js": "./client-v17.js", - "./server.js": "./server.js", - "./server-v17.js": "./server-v17.js", + "./server.js": { + "default": "./server.js", + "types": "./server.d.ts" + }, + "./server-v17.js": { + "default": "./server-v17.js", + "types": "./server-v17.d.ts" + }, "./package.json": "./package.json", "./jsx-runtime": "./jsx-runtime.js" }, @@ -36,7 +42,9 @@ "context.js", "jsx-runtime.js", "server.js", + "server.d.ts", "server-v17.js", + "server-v17.d.ts", "static-html.js", "vnode-children.js" ], diff --git a/packages/integrations/react/server-v17.js b/packages/integrations/react/server-v17.js index ad0c99622b56..16f6fcdcd9da 100644 --- a/packages/integrations/react/server-v17.js +++ b/packages/integrations/react/server-v17.js @@ -82,6 +82,7 @@ function renderToStaticMarkup(Component, props, { default: children, ...slotted } export default { + name: '@astrojs/react', check, renderToStaticMarkup, supportsAstroStaticSlot: true, diff --git a/packages/integrations/react/server.d.ts b/packages/integrations/react/server.d.ts new file mode 100644 index 000000000000..bb2f29556c14 --- /dev/null +++ b/packages/integrations/react/server.d.ts @@ -0,0 +1,2 @@ +import type { NamedSSRLoadedRendererValue } from 'astro'; +export default NamedSSRLoadedRendererValue; diff --git a/packages/integrations/react/server.js b/packages/integrations/react/server.js index c2b2558534fb..59134a6993b2 100644 --- a/packages/integrations/react/server.js +++ b/packages/integrations/react/server.js @@ -226,6 +226,7 @@ function isFormRequest(contentType) { } export default { + name: '@astrojs/react', check, renderToStaticMarkup, supportsAstroStaticSlot: true, diff --git a/packages/integrations/react/server17.d.ts b/packages/integrations/react/server17.d.ts new file mode 100644 index 000000000000..bb2f29556c14 --- /dev/null +++ b/packages/integrations/react/server17.d.ts @@ -0,0 +1,2 @@ +import type { NamedSSRLoadedRendererValue } from 'astro'; +export default NamedSSRLoadedRendererValue; diff --git a/packages/integrations/solid/src/server.ts b/packages/integrations/solid/src/server.ts index a91658ea31ae..79a911fb1821 100644 --- a/packages/integrations/solid/src/server.ts +++ b/packages/integrations/solid/src/server.ts @@ -9,6 +9,7 @@ import { } from 'solid-js/web'; import { getContext, incrementId } from './context.js'; import type { RendererContext } from './types.js'; +import type { NamedSSRLoadedRendererValue } from 'astro'; const slotName = (str: string) => str.trim().replace(/[-_]([a-z])/g, (_, w) => w.toUpperCase()); @@ -123,9 +124,12 @@ async function renderToStaticMarkup( }; } -export default { +const renderer: NamedSSRLoadedRendererValue = { + name: '@astrojs/solid', check, renderToStaticMarkup, supportsAstroStaticSlot: true, renderHydrationScript: () => generateHydrationScript(), }; + +export default renderer; diff --git a/packages/integrations/svelte/package.json b/packages/integrations/svelte/package.json index 75925bf3c15f..b9ab20e3c6f2 100644 --- a/packages/integrations/svelte/package.json +++ b/packages/integrations/svelte/package.json @@ -25,8 +25,14 @@ "./*": "./*", "./client.js": "./client.js", "./client-v5.js": "./client-v5.js", - "./server.js": "./server.js", - "./server-v5.js": "./server-v5.js", + "./server.js": { + "default": "./server.js", + "types": "./server.d.ts" + }, + "./server-v5.js": { + "default": "./server-v5.js", + "types": "./server-v5.d.ts" + }, "./package.json": "./package.json" }, "files": [ @@ -34,7 +40,9 @@ "client.js", "client-v5.js", "server.js", - "server-v5.js" + "server.d.ts", + "server-v5.js", + "server-v5.d.ts" ], "scripts": { "build": "astro-scripts build \"src/index.ts\" && astro-scripts build \"src/editor.cts\" --force-cjs --no-clean-dist && tsc", diff --git a/packages/integrations/svelte/server-v5.d.ts b/packages/integrations/svelte/server-v5.d.ts new file mode 100644 index 000000000000..bb2f29556c14 --- /dev/null +++ b/packages/integrations/svelte/server-v5.d.ts @@ -0,0 +1,2 @@ +import type { NamedSSRLoadedRendererValue } from 'astro'; +export default NamedSSRLoadedRendererValue; diff --git a/packages/integrations/svelte/server.d.ts b/packages/integrations/svelte/server.d.ts new file mode 100644 index 000000000000..bb2f29556c14 --- /dev/null +++ b/packages/integrations/svelte/server.d.ts @@ -0,0 +1,2 @@ +import type { NamedSSRLoadedRendererValue } from 'astro'; +export default NamedSSRLoadedRendererValue; diff --git a/packages/integrations/vue/package.json b/packages/integrations/vue/package.json index 5267cf918add..245347f1275b 100644 --- a/packages/integrations/vue/package.json +++ b/packages/integrations/vue/package.json @@ -24,13 +24,17 @@ "./editor": "./dist/editor.cjs", "./*": "./*", "./client.js": "./client.js", - "./server.js": "./server.js", + "./server.js": { + "default": "./server.js", + "types": "./server.d.ts" + }, "./package.json": "./package.json" }, "files": [ "dist", "client.js", "server.js", + "server.d.ts", "static-html.js" ], "scripts": { diff --git a/packages/integrations/vue/server.d.ts b/packages/integrations/vue/server.d.ts new file mode 100644 index 000000000000..bb2f29556c14 --- /dev/null +++ b/packages/integrations/vue/server.d.ts @@ -0,0 +1,2 @@ +import type { NamedSSRLoadedRendererValue } from 'astro'; +export default NamedSSRLoadedRendererValue; diff --git a/packages/integrations/vue/server.js b/packages/integrations/vue/server.js index be57dd43ab94..afdc9e8a2dae 100644 --- a/packages/integrations/vue/server.js +++ b/packages/integrations/vue/server.js @@ -27,6 +27,7 @@ async function renderToStaticMarkup(Component, inputProps, slotted, metadata) { } export default { + name: '@astrojs/vue', check, renderToStaticMarkup, supportsAstroStaticSlot: true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae9663178cac..f4e3889b2eea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2539,11 +2539,14 @@ importers: specifier: workspace:* version: link:../../.. - packages/astro/test/fixtures/container-react: + packages/astro/test/fixtures/container-custom-renderers: dependencies: '@astrojs/react': specifier: workspace:* version: link:../../../../integrations/react + '@astrojs/vue': + specifier: workspace:* + version: link:../../../../integrations/vue astro: specifier: workspace:* version: link:../../.. @@ -2553,6 +2556,9 @@ importers: react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + vue: + specifier: ^3.4.27 + version: 3.4.27(typescript@5.4.5) packages/astro/test/fixtures/content: dependencies: