0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-03-10 23:01:26 -05:00

fix: prevent client hydration when rendering via Container API

This commit is contained in:
Emanuele Stoppa 2024-06-26 13:50:13 +01:00
parent 0df81422a8
commit ed69f8f98e
12 changed files with 50 additions and 6 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixes a bug in the Container API that was triggered when trying to render a client component with some `client:*` directives.

View file

@ -2,4 +2,4 @@
import Counter from './Counter.jsx';
---
<Counter initialCount={5} />
<Counter initialCount={5} client:idle />

View file

@ -3220,6 +3220,12 @@ export interface SSRResult {
response: AstroGlobal['response'];
request: AstroGlobal['request'];
actionResult?: ReturnType<AstroGlobal['getActionResult']>;
// Metadata used to signal Astro renderer to skip any client hydration, such as:
// - client directories
// - client entrypoint
// - renderer-url
skipHydration: boolean;
renderers: SSRLoadedRenderer[];
/**
* Map of directive name (e.g. `load`) to the directive script code

View file

@ -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';
@ -114,7 +115,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(),
@ -435,6 +436,8 @@ export class experimental_AstroContainer {
pathname: url.pathname,
locals: options?.locals ?? {},
});
// client directives aren't needed in this case
renderContext.skipHydration = true;
if (options.params) {
renderContext.params = options.params;
}

View file

@ -42,6 +42,9 @@ export class RenderContext {
// The first route that this instance of the context attempts to render
originalRoute: RouteData;
// Metadata used to signal Astro renderer to skip any client hydration
skipHydration: boolean;
private constructor(
readonly pipeline: Pipeline,
public locals: App.Locals,
@ -56,6 +59,7 @@ export class RenderContext {
public props: Props = {}
) {
this.originalRoute = routeData;
this.skipHydration = false;
}
/**
@ -297,7 +301,7 @@ export class RenderContext {
}
async createResult(mod: ComponentInstance) {
const { cookies, pathname, pipeline, routeData, status } = this;
const { cookies, pathname, pipeline, routeData, status, skipHydration } = this;
const { clientDirectives, inlinedScripts, compressHTML, manifest, renderers, resolve } =
pipeline;
const { links, scripts, styles } = await pipeline.headElements(routeData);
@ -343,6 +347,7 @@ export class RenderContext {
request: this.request,
scripts,
styles,
skipHydration,
actionResult,
_metadata: {
hasHydrationScript: false,

View file

@ -156,7 +156,7 @@ export async function generateHydrateScript(
island.props['component-url'] = await result.resolve(decodeURI(componentUrl));
// Add renderer url
if (renderer.clientEntrypoint) {
if (renderer.clientEntrypoint && !result.skipHydration) {
island.props['component-export'] = componentExport.value;
island.props['renderer-url'] = await result.resolve(decodeURI(renderer.clientEntrypoint));
island.props['props'] = escapeHTML(serializeProps(props, metadata));

View file

@ -295,7 +295,8 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
renderer &&
!renderer.clientEntrypoint &&
renderer.name !== '@astrojs/lit' &&
metadata.hydrate
metadata.hydrate &&
!result.skipHydration
) {
throw new AstroError({
...AstroErrorData.NoClientEntrypoint,

View file

@ -261,4 +261,12 @@ 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, /I am a react button/);
});
});

View file

@ -0,0 +1,5 @@
---
import Button from "./button.jsx"
---
<Button client:idle />

View file

@ -0,0 +1,10 @@
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 });
return await container.renderToResponse(Component);
}

View file

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

View file

@ -60,6 +60,7 @@ function getViteConfiguration(
'react/jsx-runtime',
'react/jsx-dev-runtime',
'react-dom',
``,
],
exclude: [reactConfig.server],
},