0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-16 21:46:22 -05:00

feat: refine container APIs for renderers (#11251)

This commit is contained in:
Emanuele Stoppa 2024-06-14 06:52:17 +01:00 committed by GitHub
parent 1b42229f51
commit fd9da98b19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 160 additions and 31 deletions

View file

@ -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);
}

View file

@ -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<boolean>;
renderToStaticMarkup: AsyncRendererComponentFn<{
html: string;

View file

@ -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;
}

View file

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

View file

@ -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/);
});
});

View file

@ -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()],
});

View file

@ -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"
}
}

View file

@ -0,0 +1,3 @@
<template>
<button id="arrow-fn-component">I am a vue button</button>
</template>

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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": {

2
packages/integrations/lit/server.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
import type { NamedSSRLoadedRendererValue } from 'astro';
export default NamedSSRLoadedRendererValue;

View file

@ -112,6 +112,7 @@ async function renderToStaticMarkup(Component, props, slots) {
}
export default {
name: '@astrojs/lit',
check,
renderToStaticMarkup,
};

View file

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

View file

@ -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"
],

View file

@ -82,6 +82,7 @@ function renderToStaticMarkup(Component, props, { default: children, ...slotted
}
export default {
name: '@astrojs/react',
check,
renderToStaticMarkup,
supportsAstroStaticSlot: true,

View file

@ -0,0 +1,2 @@
import type { NamedSSRLoadedRendererValue } from 'astro';
export default NamedSSRLoadedRendererValue;

View file

@ -226,6 +226,7 @@ function isFormRequest(contentType) {
}
export default {
name: '@astrojs/react',
check,
renderToStaticMarkup,
supportsAstroStaticSlot: true,

View file

@ -0,0 +1,2 @@
import type { NamedSSRLoadedRendererValue } from 'astro';
export default NamedSSRLoadedRendererValue;

View file

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

View file

@ -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",

View file

@ -0,0 +1,2 @@
import type { NamedSSRLoadedRendererValue } from 'astro';
export default NamedSSRLoadedRendererValue;

View file

@ -0,0 +1,2 @@
import type { NamedSSRLoadedRendererValue } from 'astro';
export default NamedSSRLoadedRendererValue;

View file

@ -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": {

2
packages/integrations/vue/server.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
import type { NamedSSRLoadedRendererValue } from 'astro';
export default NamedSSRLoadedRendererValue;

View file

@ -27,6 +27,7 @@ async function renderToStaticMarkup(Component, inputProps, slotted, metadata) {
}
export default {
name: '@astrojs/vue',
check,
renderToStaticMarkup,
supportsAstroStaticSlot: true,

View file

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