mirror of
https://github.com/withastro/astro.git
synced 2025-01-20 22:12:38 -05:00
feat(container): client hydration (#11486)
* fix: prevent client hydration when rendering via Container API * revert change that is not needed * skip client directives via option * reword changeset * Fix types of react server.d.ts * add new API --------- Co-authored-by: Matthew Phillips <matthew@skypack.dev>
This commit is contained in:
parent
aa05be3313
commit
9c0c8492d9
8 changed files with 99 additions and 5 deletions
15
.changeset/afraid-cups-deliver.md
Normal file
15
.changeset/afraid-cups-deliver.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Adds a new function called `addClientRenderer` to the Container API.
|
||||
|
||||
This function should be used when rendering components using the `client:*` directives. The `addClientRenderer` API must be used
|
||||
*after* the use of the `addServerRenderer`:
|
||||
|
||||
```js
|
||||
const container = await experimental_AstroContainer.create();
|
||||
container.addServerRenderer({renderer});
|
||||
container.addClientRenderer({name: '@astrojs/react', entrypoint: '@astrojs/react/client.js'});
|
||||
const response = await container.renderToResponse(Component);
|
||||
```
|
|
@ -14,6 +14,7 @@ import type {
|
|||
SSRManifest,
|
||||
SSRResult,
|
||||
} from '../@types/astro.js';
|
||||
import { getDefaultClientDirectives } from '../core/client-directive/index.js';
|
||||
import { ASTRO_CONFIG_DEFAULTS } from '../core/config/schema.js';
|
||||
import { validateConfig } from '../core/config/validate.js';
|
||||
import { Logger } from '../core/logger/core.js';
|
||||
|
@ -96,6 +97,11 @@ export type AddServerRenderer =
|
|||
name: string;
|
||||
};
|
||||
|
||||
export type AddClientRenderer = {
|
||||
name: string;
|
||||
entrypoint: string;
|
||||
};
|
||||
|
||||
function createManifest(
|
||||
manifest?: AstroContainerManifest,
|
||||
renderers?: SSRLoadedRenderer[],
|
||||
|
@ -116,7 +122,7 @@ function createManifest(
|
|||
entryModules: manifest?.entryModules ?? {},
|
||||
routes: manifest?.routes ?? [],
|
||||
adapterName: '',
|
||||
clientDirectives: manifest?.clientDirectives ?? new Map(),
|
||||
clientDirectives: manifest?.clientDirectives ?? getDefaultClientDirectives(),
|
||||
renderers: renderers ?? manifest?.renderers ?? [],
|
||||
base: manifest?.base ?? ASTRO_CONFIG_DEFAULTS.base,
|
||||
componentMetadata: manifest?.componentMetadata ?? new Map(),
|
||||
|
@ -283,7 +289,7 @@ export class experimental_AstroContainer {
|
|||
}
|
||||
|
||||
/**
|
||||
* Use this function to manually add a renderer to the container.
|
||||
* Use this function to manually add a **server** renderer to the container.
|
||||
*
|
||||
* This function is preferred when you require to use the container with a renderer in environments such as on-demand pages.
|
||||
*
|
||||
|
@ -326,6 +332,46 @@ export class experimental_AstroContainer {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this function to manually add a **client** renderer to the container.
|
||||
*
|
||||
* When rendering components that use the `client:*` directives, you need to use this function.
|
||||
*
|
||||
* ## Example
|
||||
*
|
||||
* ```js
|
||||
* import reactRenderer from "@astrojs/react/server.js";
|
||||
* import { experimental_AstroContainer as AstroContainer } from "astro/container"
|
||||
*
|
||||
* const container = await AstroContainer.create();
|
||||
* container.addServerRenderer(reactRenderer);
|
||||
* container.addClientRenderer({
|
||||
* name: "@astrojs/react",
|
||||
* entrypoint: "@astrojs/react/client.js"
|
||||
* });
|
||||
* ```
|
||||
*
|
||||
* @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.entrypoint The entrypoint of the client renderer.
|
||||
*/
|
||||
public addClientRenderer(options: AddClientRenderer): void {
|
||||
const { entrypoint, name } = options;
|
||||
|
||||
const rendererIndex = this.#pipeline.manifest.renderers.findIndex((r) => r.name === name);
|
||||
if (rendererIndex === -1) {
|
||||
throw new Error(
|
||||
'You tried to add the ' +
|
||||
name +
|
||||
" client renderer, but its server renderer wasn't added. You must add the server renderer first. Use the `addServerRenderer` function."
|
||||
);
|
||||
}
|
||||
const renderer = this.#pipeline.manifest.renderers[rendererIndex];
|
||||
renderer.clientEntrypoint = entrypoint;
|
||||
|
||||
this.#pipeline.manifest.renderers[rendererIndex] = renderer;
|
||||
}
|
||||
|
||||
// NOTE: we keep this private via TS instead via `#` so it's still available on the surface, so we can play with it.
|
||||
// @ematipico: I plan to use it for a possible integration that could help people
|
||||
private static async createFromManifest(
|
||||
|
|
|
@ -521,7 +521,10 @@ export async function renderComponent(
|
|||
);
|
||||
|
||||
function handleCancellation(e: unknown) {
|
||||
if (result.cancelled) return { render() {} };
|
||||
if (result.cancelled)
|
||||
return {
|
||||
render() {},
|
||||
};
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -261,4 +261,13 @@ describe('Container with renderers', () => {
|
|||
|
||||
assert.match(html, /I am a vue button/);
|
||||
});
|
||||
|
||||
it('Should render a component with directives', async () => {
|
||||
const request = new Request('https://example.com/button-directive');
|
||||
const response = await app.render(request);
|
||||
const html = await response.text();
|
||||
|
||||
assert.match(html, /Button not rendered/);
|
||||
assert.match(html, /I am a react button/);
|
||||
});
|
||||
});
|
||||
|
|
8
packages/astro/test/fixtures/container-custom-renderers/src/components/buttonDirective.astro
vendored
Normal file
8
packages/astro/test/fixtures/container-custom-renderers/src/components/buttonDirective.astro
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
import Button from "./button.jsx"
|
||||
---
|
||||
|
||||
<div>
|
||||
<p>Button not rendered</p>
|
||||
<Button client:idle/>
|
||||
</div>
|
11
packages/astro/test/fixtures/container-custom-renderers/src/pages/button-directive.ts
vendored
Normal file
11
packages/astro/test/fixtures/container-custom-renderers/src/pages/button-directive.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
import type { APIRoute, SSRLoadedRenderer } from 'astro';
|
||||
import { experimental_AstroContainer } from 'astro/container';
|
||||
import renderer from '@astrojs/react/server.js';
|
||||
import Component from '../components/buttonDirective.astro';
|
||||
|
||||
export const GET: APIRoute = async (ctx) => {
|
||||
const container = await experimental_AstroContainer.create();
|
||||
container.addServerRenderer({ renderer });
|
||||
container.addClientRenderer({ name: '@astrojs/react', entrypoint: '@astrojs/react/client.js' });
|
||||
return await container.renderToResponse(Component);
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
import type {APIRoute, SSRLoadedRenderer} from "astro";
|
||||
import type {APIRoute} from "astro";
|
||||
import { experimental_AstroContainer } from "astro/container";
|
||||
import renderer from '@astrojs/react/server.js';
|
||||
import Component from "../components/button.jsx"
|
||||
|
|
4
packages/integrations/react/server.d.ts
vendored
4
packages/integrations/react/server.d.ts
vendored
|
@ -1,2 +1,4 @@
|
|||
import type { NamedSSRLoadedRendererValue } from 'astro';
|
||||
export default NamedSSRLoadedRendererValue;
|
||||
|
||||
declare const renderer: NamedSSRLoadedRendererValue;
|
||||
export default renderer;
|
||||
|
|
Loading…
Add table
Reference in a new issue