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:
parent
1b42229f51
commit
fd9da98b19
28 changed files with 160 additions and 31 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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/);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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()],
|
||||
});
|
|
@ -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"
|
||||
}
|
||||
}
|
3
packages/astro/test/fixtures/container-custom-renderers/src/components/button.vue
vendored
Normal file
3
packages/astro/test/fixtures/container-custom-renderers/src/components/button.vue
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
<template>
|
||||
<button id="arrow-fn-component">I am a vue button</button>
|
||||
</template>
|
|
@ -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);
|
||||
}
|
10
packages/astro/test/fixtures/container-custom-renderers/src/pages/vue.ts
vendored
Normal file
10
packages/astro/test/fixtures/container-custom-renderers/src/pages/vue.ts
vendored
Normal 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);
|
||||
}
|
|
@ -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
2
packages/integrations/lit/server.d.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
import type { NamedSSRLoadedRendererValue } from 'astro';
|
||||
export default NamedSSRLoadedRendererValue;
|
|
@ -112,6 +112,7 @@ async function renderToStaticMarkup(Component, props, slots) {
|
|||
}
|
||||
|
||||
export default {
|
||||
name: '@astrojs/lit',
|
||||
check,
|
||||
renderToStaticMarkup,
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
],
|
||||
|
|
|
@ -82,6 +82,7 @@ function renderToStaticMarkup(Component, props, { default: children, ...slotted
|
|||
}
|
||||
|
||||
export default {
|
||||
name: '@astrojs/react',
|
||||
check,
|
||||
renderToStaticMarkup,
|
||||
supportsAstroStaticSlot: true,
|
||||
|
|
2
packages/integrations/react/server.d.ts
vendored
Normal file
2
packages/integrations/react/server.d.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
import type { NamedSSRLoadedRendererValue } from 'astro';
|
||||
export default NamedSSRLoadedRendererValue;
|
|
@ -226,6 +226,7 @@ function isFormRequest(contentType) {
|
|||
}
|
||||
|
||||
export default {
|
||||
name: '@astrojs/react',
|
||||
check,
|
||||
renderToStaticMarkup,
|
||||
supportsAstroStaticSlot: true,
|
||||
|
|
2
packages/integrations/react/server17.d.ts
vendored
Normal file
2
packages/integrations/react/server17.d.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
import type { NamedSSRLoadedRendererValue } from 'astro';
|
||||
export default NamedSSRLoadedRendererValue;
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
2
packages/integrations/svelte/server-v5.d.ts
vendored
Normal file
2
packages/integrations/svelte/server-v5.d.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
import type { NamedSSRLoadedRendererValue } from 'astro';
|
||||
export default NamedSSRLoadedRendererValue;
|
2
packages/integrations/svelte/server.d.ts
vendored
Normal file
2
packages/integrations/svelte/server.d.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
import type { NamedSSRLoadedRendererValue } from 'astro';
|
||||
export default NamedSSRLoadedRendererValue;
|
|
@ -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
2
packages/integrations/vue/server.d.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
import type { NamedSSRLoadedRendererValue } from 'astro';
|
||||
export default NamedSSRLoadedRendererValue;
|
|
@ -27,6 +27,7 @@ async function renderToStaticMarkup(Component, inputProps, slotted, metadata) {
|
|||
}
|
||||
|
||||
export default {
|
||||
name: '@astrojs/vue',
|
||||
check,
|
||||
renderToStaticMarkup,
|
||||
supportsAstroStaticSlot: true,
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue