Skip to content

Commit

Permalink
add new API
Browse files Browse the repository at this point in the history
  • Loading branch information
ematipico committed Jul 18, 2024
1 parent 865e97b commit 33a8cba
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 35 deletions.
11 changes: 7 additions & 4 deletions .changeset/afraid-cups-deliver.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
'astro': patch
---

Adds a new option to the Container API to skip client side directives. This option should be used if you render a component that uses `client:*` directives.
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();
return await container.renderToResponse(Component, {
skipClientDirectives: true,
});
container.addServerRenderer({renderer});
container.addClientRenderer({name: '@astrojs/react', entrypoint: '@astrojs/react/client.js'});
const response = await container.renderToResponse(Component);
```
6 changes: 0 additions & 6 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3339,12 +3339,6 @@ 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
Expand Down
57 changes: 46 additions & 11 deletions packages/astro/src/container/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,6 @@ export type ContainerRenderOptions = {
* ```
*/
props?: Props;

/**
* Allows to bypass clientside hydration of components.
*
* If you're testing components that use `client:*` directives, you might want to use this option.
*/
skipClientDirectives?: boolean;
};

export type AddServerRenderer =
Expand All @@ -103,6 +96,11 @@ export type AddServerRenderer =
name: string;
};

export type AddClientRenderer = {
name: string;
entrypoint: string;
};

function createManifest(
manifest?: AstroContainerManifest,
renderers?: SSRLoadedRenderer[],
Expand Down Expand Up @@ -289,7 +287,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.
*
Expand Down Expand Up @@ -332,6 +330,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(
Expand Down Expand Up @@ -443,9 +481,6 @@ export class experimental_AstroContainer {
pathname: url.pathname,
locals: options?.locals ?? {},
});
if (options.skipClientDirectives === true) {
renderContext.skipHydration = true;
}
if (options.params) {
renderContext.params = options.params;
}
Expand Down
7 changes: 1 addition & 6 deletions packages/astro/src/core/render-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@ 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,
Expand All @@ -61,7 +58,6 @@ export class RenderContext {
public props: Props = {}
) {
this.originalRoute = routeData;
this.skipHydration = false;
}

/**
Expand Down Expand Up @@ -323,7 +319,7 @@ export class RenderContext {
}

async createResult(mod: ComponentInstance) {
const { cookies, pathname, pipeline, routeData, status, skipHydration } = this;
const { cookies, pathname, pipeline, routeData, status } = this;
const { clientDirectives, inlinedScripts, compressHTML, manifest, renderers, resolve } =
pipeline;
const { links, scripts, styles } = await pipeline.headElements(routeData);
Expand Down Expand Up @@ -370,7 +366,6 @@ export class RenderContext {
request: this.request,
scripts,
styles,
skipHydration,
actionResult,
serverIslandNameMap: manifest.serverIslandNameMap ?? new Map(),
_metadata: {
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/runtime/server/hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export async function generateHydrateScript(
island.props['component-url'] = await result.resolve(decodeURI(componentUrl));

// Add renderer url
if (renderer.clientEntrypoint && !result.skipHydration) {
if (renderer.clientEntrypoint) {
island.props['component-export'] = componentExport.value;
island.props['renderer-url'] = await result.resolve(decodeURI(renderer.clientEntrypoint));
island.props['props'] = escapeHTML(serializeProps(props, metadata));
Expand Down
8 changes: 5 additions & 3 deletions packages/astro/src/runtime/server/render/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,7 @@ 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 &&
!result.skipHydration
metadata.hydrate
) {
throw new AstroError({
...AstroErrorData.NoClientEntrypoint,
Expand Down Expand Up @@ -522,7 +521,10 @@ export async function renderComponent(
);

function handleCancellation(e: unknown) {
if (result.cancelled) return { render() {} };
if (result.cancelled)
return {
render() {},
};
throw e;
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/astro/test/container.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ describe('Container with renderers', () => {
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/);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
import Button from "./button.jsx"
---

<Button client:idle />
<div>
<p>Button not rendered</p>
<Button client:idle/>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ 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, {
skipClientDirectives: true,
});
container.addClientRenderer({ name: '@astrojs/react', entrypoint: '@astrojs/react/client.js' });
return await container.renderToResponse(Component);
};

0 comments on commit 33a8cba

Please sign in to comment.