Skip to content

Commit

Permalink
Add more server rendering hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
lemonmade committed Oct 15, 2024
1 parent 5a8036d commit d409c19
Show file tree
Hide file tree
Showing 10 changed files with 88 additions and 24 deletions.
6 changes: 6 additions & 0 deletions .changeset/afraid-rings-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@quilted/preact-browser': patch
'@quilted/quilt': patch
---

Add more server rendering hooks
5 changes: 5 additions & 0 deletions packages/preact-browser/source/context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import {createOptionalContext} from '@quilted/preact-context';
import type {BrowserDetails} from '@quilted/browser';
import type {BrowserAssets} from '@quilted/assets';

export const BrowserDetailsContext = createOptionalContext<BrowserDetails>();
export const useBrowserDetails = BrowserDetailsContext.use;

export const BrowserAssetsManifestContext =
createOptionalContext<BrowserAssets>();
export const useBrowserAssetsManifest = BrowserAssetsManifestContext.use;
7 changes: 6 additions & 1 deletion packages/preact-browser/source/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
export * from '@quilted/browser';

export {render, hydrate} from './browser.tsx';
export {BrowserDetailsContext, useBrowserDetails} from './context.ts';
export {
BrowserDetailsContext,
useBrowserDetails,
BrowserAssetsManifestContext,
useBrowserAssetsManifest,
} from './context.ts';

export {useAlternateUrl} from './hooks/alternate-url.ts';
export {useBodyAttributes} from './hooks/body-attributes.ts';
Expand Down
8 changes: 7 additions & 1 deletion packages/preact-browser/source/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ export {
export type {AssetLoadTiming} from '@quilted/assets';
export * from '@quilted/browser/server';

export {BrowserDetailsContext} from './context.ts';
export {
BrowserDetailsContext,
useBrowserDetails,
BrowserAssetsManifestContext,
useBrowserAssetsManifest,
} from './context.ts';

export {useModuleAssets} from './server/hooks/assets.ts';
export {useBrowserResponse} from './server/hooks/browser-response.ts';
export {useBrowserResponseAction} from './server/hooks/browser-response-action.ts';
export {useCacheControl} from './server/hooks/cache-control.ts';
export {useContentSecurityPolicy} from './server/hooks/content-security-policy.ts';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {useBrowserDetails} from '../../context.ts';

/**
* During server-side rendering, the function you pass to this hook is
* called with the HTTP server-rendering manager, if one is found.
* called with the mutable browser response object, if one is found.
* You typically shouldn’t need to call this hook directly, as all
* of the individual actions you can perform on the HTTP manager are
* exposed as dedicated hooks.
Expand Down
29 changes: 29 additions & 0 deletions packages/preact-browser/source/server/hooks/browser-response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {BrowserResponse} from '@quilted/browser/server';

import {useBrowserDetails} from '../../context.ts';

/**
* Returns the mutable browser response object. This hook can only be called on the
* server, and only when a `BrowserResponse` is found in the context. This object gives
* you access to details about the original request, and the ability to read and write
* headers and head tags to the eventual response.
*
* When using the `renderToHTMLResponse()` or `renderToHTMLString()` functions, this context
* is automatically provided for you. If you are using Preact’s server rendering functions
* directly, you will need to provide a `BrowserResponse` object yourself.
*/
export function useBrowserResponse() {
const response = useBrowserDetails();

if (typeof document === 'object') {
throw new Error(
`You can only call the useBrowserResponse() hook in server-side code.`,
);
}

if (!(response instanceof BrowserResponse)) {
throw new Error(`No BrowserResponse found in context.`);
}

return response;
}
5 changes: 5 additions & 0 deletions packages/quilt/source/server.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@ export {noopCreateRequestRouterLocalization as createRequestRouterLocalization};

function noopRenderToResponse() {}
export {noopRenderToResponse as renderToResponse};

function NoopServerContext() {
return null;
}
export {NoopServerContext as ServerContext};
2 changes: 1 addition & 1 deletion packages/quilt/source/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export {createRequestRouterLocalization} from '@quilted/preact-localize/request-

export {
renderToResponse,
renderToStringWithServerContext,
type RenderToResponseOptions,
type RenderHTMLFunction,
} from './server/request-router.tsx';
export {ServerContext} from './server/ServerContext.tsx';
20 changes: 18 additions & 2 deletions packages/quilt/source/server/ServerContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@ import type {RenderableProps, VNode} from 'preact';

import {
BrowserDetailsContext,
BrowserAssetsManifestContext,
type BrowserDetails,
} from '@quilted/preact-browser/server';

import {type BrowserAssets} from '@quilted/assets';

interface Props {
browser?: BrowserDetails;
assets?: BrowserAssets;
}

export function ServerContext({browser, children}: RenderableProps<Props>) {
export function ServerContext({
browser,
assets,
children,
}: RenderableProps<Props>) {
const withBrowser = browser ? (
<BrowserDetailsContext.Provider value={browser}>
{children}
Expand All @@ -18,5 +26,13 @@ export function ServerContext({browser, children}: RenderableProps<Props>) {
children
);

return withBrowser as VNode<{}>;
const withAssets = assets ? (
<BrowserAssetsManifestContext.Provider value={assets}>
{withBrowser}
</BrowserAssetsManifestContext.Provider>
) : (
withBrowser
);

return withAssets as VNode<{}>;
}
28 changes: 10 additions & 18 deletions packages/quilt/source/server/request-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
type BrowserAssetsEntry,
} from '@quilted/assets';
import {
BrowserDetails,
BrowserResponse,
ScriptAsset,
ScriptAssetPreload,
Expand All @@ -23,17 +22,6 @@ import {HTMLResponse, EnhancedResponse} from '@quilted/request-router';

import {ServerContext} from './ServerContext.tsx';

export async function renderToStringWithServerContext(
element: VNode<any>,
{browser}: {browser?: BrowserDetails} = {},
) {
const rendered = await renderToStringAsync(
<ServerContext browser={browser}>{element}</ServerContext>,
);

return rendered;
}

export interface RenderHTMLFunction {
(
content: ReadableStream<string>,
Expand Down Expand Up @@ -101,9 +89,11 @@ export async function renderToResponse(
let rendered: string;

try {
rendered = await renderToStringWithServerContext(element, {
browser: browserResponse,
});
rendered = await renderToStringAsync(
<ServerContext assets={assets} browser={browserResponse}>
{element}
</ServerContext>,
);
} catch (error) {
if (error instanceof Response) {
const mergedHeaders = new Headers(browserResponse.headers);
Expand Down Expand Up @@ -149,9 +139,11 @@ export async function renderToResponse(
// this, they will explicitly turn on streaming and will have to use some in-app
// to manually handle redirects (e.g., by rendering a script tag that uses JavaScript
// to redirect)
const rendered = await renderToStringWithServerContext(element, {
browser: browserResponse,
});
const rendered = await renderToStringAsync(
<ServerContext assets={assets} browser={browserResponse}>
{element}
</ServerContext>,
);

appWriter.write(rendered);
}
Expand Down

0 comments on commit d409c19

Please sign in to comment.