Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace server context with AsyncLocalStorage and client context #20

Merged
merged 1 commit into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 0 additions & 29 deletions apps/cloudflare-app/src/worker/create-rsc-app-options.tsx

This file was deleted.

11 changes: 11 additions & 0 deletions apps/cloudflare-app/src/worker/create-rsc-app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// This is in a separate file so that we can configure webpack to use the
// `react-server` layer for this module, and therefore the imported modules
// (React and the server components) will be imported with the required
// `react-server` condition.

import {App} from '@mfng/shared-app/app.js';
import * as React from 'react';

export function createRscApp(): React.ReactNode {
return <App getTitle={(pathname) => `Cloudflare RSC/SSR demo ${pathname}`} />;
}
37 changes: 21 additions & 16 deletions apps/cloudflare-app/src/worker/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {routerLocationAsyncLocalStorage} from '@mfng/core/router-location-async-local-storage';
import type {ServerManifest} from '@mfng/core/server/rsc';
import {createRscActionStream, createRscAppStream} from '@mfng/core/server/rsc';
import {createHtmlStream} from '@mfng/core/server/ssr';
import type {ClientManifest, SSRManifest} from 'react-server-dom-webpack';
import {createRscAppOptions} from './create-rsc-app-options.js';
import {createRscApp} from './create-rsc-app.js';
import type {EnvWithStaticContent, HandlerParams} from './get-json-from-kv.js';
import {getJsonFromKv} from './get-json-from-kv.js';

Expand All @@ -24,25 +25,29 @@ const handleGet: ExportedHandlerFetchHandler<EnvWithStaticContent> = async (
getJsonFromKv<Record<string, string>>(`client/css-manifest.json`, params),
]);

const rscAppStream = createRscAppStream({
...createRscAppOptions({requestUrl: request.url}),
reactClientManifest,
mainCssHref: cssManifest[`main.css`]!,
});
const {pathname, search} = new URL(request.url);

if (request.headers.get(`accept`) === `text/x-component`) {
return new Response(rscAppStream, {
headers: {'Content-Type': `text/x-component; charset=utf-8`},
return routerLocationAsyncLocalStorage.run({pathname, search}, async () => {
const rscAppStream = createRscAppStream({
app: createRscApp(),
reactClientManifest,
mainCssHref: cssManifest[`main.css`]!,
});
}

const htmlStream = await createHtmlStream(rscAppStream, {
reactSsrManifest,
bootstrapScripts: [jsManifest[`main.js`]!],
});
if (request.headers.get(`accept`) === `text/x-component`) {
return new Response(rscAppStream, {
headers: {'Content-Type': `text/x-component; charset=utf-8`},
});
}

const htmlStream = await createHtmlStream(rscAppStream, {
reactSsrManifest,
bootstrapScripts: [jsManifest[`main.js`]!],
});

return new Response(htmlStream, {
headers: {'Content-Type': `text/html; charset=utf-8`},
return new Response(htmlStream, {
headers: {'Content-Type': `text/html; charset=utf-8`},
});
});
};

Expand Down
11 changes: 8 additions & 3 deletions apps/cloudflare-app/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,24 @@ export default function createConfigs(_env, argv) {
},
resolve: {
plugins: [new ResolveTypeScriptPlugin()],
conditionNames: [`workerd`, `...`],
conditionNames: [`workerd`, `node`, `...`],
},
module: {
rules: [
{
resource: (value) =>
/core\/lib\/server\/rsc\.js$/.test(value) ||
/create-rsc-app-options\.tsx$/.test(value),
/create-rsc-app\.tsx$/.test(value),
layer: webpackRscLayerName,
},
{
// AsyncLocalStorage module instances must be in a shared layer.
layer: `shared`,
test: /(router-location-async-local-storage|core\/lib\/server\/use-router-location\.js)/,
},
{
issuerLayer: webpackRscLayerName,
resolve: {conditionNames: [`react-server`, `...`]},
resolve: {conditionNames: [`react-server`, `node`, `...`]},
},
{
oneOf: [
Expand Down
6 changes: 3 additions & 3 deletions apps/shared-app/src/client/countries-search.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
'use client';

import {useRouter} from '@mfng/core/client';
import {useRouterLocation} from '@mfng/core/use-router-location';
import * as React from 'react';
import {LocationServerContext} from '../shared/location-server-context.js';

export function CountriesSearch(): JSX.Element {
const location = React.useContext(LocationServerContext);
const {search} = useRouterLocation();
const {replace} = useRouter();
const [, startTransition] = React.useTransition();

const [query, setQuery] = React.useState(
() => new URL(location).searchParams.get(`q`) || ``,
() => new URLSearchParams(search).get(`q`) || ``,
);

const handleChange: React.ChangeEventHandler<HTMLInputElement> = (event) => {
Expand Down
19 changes: 8 additions & 11 deletions apps/shared-app/src/server/app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useRouterLocation} from '@mfng/core/use-router-location';
import * as React from 'react';
import {NavigationContainer} from '../client/navigation-container.js';
import {LocationServerContext} from '../shared/location-server-context.js';
import {Navigation} from '../shared/navigation.js';
import {Routes} from './routes.js';

Expand All @@ -9,8 +9,7 @@ export interface AppProps {
}

export function App({getTitle}: AppProps): JSX.Element {
const location = React.useContext(LocationServerContext);
const {pathname} = new URL(location);
const {pathname} = useRouterLocation();

return (
<html>
Expand All @@ -21,14 +20,12 @@ export function App({getTitle}: AppProps): JSX.Element {
<link rel="icon" href="/client/favicon.ico" type="image/x-icon" />
</head>
<body>
<LocationServerContext.Provider value={location}>
<React.Suspense>
<Navigation />
<NavigationContainer>
<Routes />
</NavigationContainer>
</React.Suspense>
</LocationServerContext.Provider>
<React.Suspense>
<Navigation />
<NavigationContainer>
<Routes />
</NavigationContainer>
</React.Suspense>
</body>
</html>
);
Expand Down
6 changes: 3 additions & 3 deletions apps/shared-app/src/server/countries-list.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {useRouterLocation} from '@mfng/core/use-router-location';
import * as React from 'react';
import {LocationServerContext} from '../shared/location-server-context.js';
import {countriesFuse} from './countries-fuse.js';

export function CountriesList(): JSX.Element {
const location = React.useContext(LocationServerContext);
const query = new URL(location).searchParams.get(`q`);
const {search} = useRouterLocation();
const query = new URLSearchParams(search).get(`q`);

if (!query) {
return (
Expand Down
5 changes: 2 additions & 3 deletions apps/shared-app/src/server/routes.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import {useRouterLocation} from '@mfng/core/use-router-location';
import * as React from 'react';
import {LocationServerContext} from '../shared/location-server-context.js';
import {FastPage} from './fast-page.js';
import {HomePage} from './home-page.js';
import {SlowPage} from './slow-page.js';

export function Routes(): JSX.Element {
const location = React.useContext(LocationServerContext);
const {pathname} = new URL(location);
const {pathname} = useRouterLocation();

switch (pathname) {
case `/slow-page`:
Expand Down
8 changes: 0 additions & 8 deletions apps/shared-app/src/shared/location-server-context.ts

This file was deleted.

6 changes: 3 additions & 3 deletions apps/shared-app/src/shared/navigation-item.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useRouterLocation} from '@mfng/core/use-router-location';
import * as React from 'react';
import {Link} from '../client/link.js';
import {LocationServerContext} from './location-server-context.js';

export type NavigationItemProps = React.PropsWithChildren<{
readonly pathname: string;
Expand All @@ -10,9 +10,9 @@ export function NavigationItem({
children,
pathname,
}: NavigationItemProps): JSX.Element {
const location = React.useContext(LocationServerContext);
const {pathname: currentPathname} = useRouterLocation();

if (pathname === new URL(location).pathname) {
if (pathname === currentPathname) {
return (
<span className="inline-block rounded-md bg-zinc-800 py-1 px-3 text-zinc-50">
{children}
Expand Down

This file was deleted.

13 changes: 13 additions & 0 deletions apps/vercel-app/src/edge-function-handler/create-rsc-app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// This is in a separate file so that we can configure webpack to use the
// `react-server` layer for this module, and therefore the imported modules
// (React and the server components) will be imported with the required
// `react-server` condition.

import {App} from '@mfng/shared-app/app.js';
import * as React from 'react';

export function createRscApp(): React.ReactNode {
return (
<App getTitle={(pathname) => `Vercel Edge RSC/SSR demo ${pathname}`} />
);
}
50 changes: 28 additions & 22 deletions apps/vercel-app/src/edge-function-handler/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {routerLocationAsyncLocalStorage} from '@mfng/core/router-location-async-local-storage';
import {createRscActionStream, createRscAppStream} from '@mfng/core/server/rsc';
import {createHtmlStream} from '@mfng/core/server/ssr';
import {createRscAppOptions} from './create-rsc-app-options.js';
import {createRscApp} from './create-rsc-app.js';
import {
cssManifest,
jsManifest,
Expand Down Expand Up @@ -29,32 +30,37 @@ export default async function handler(request: Request): Promise<Response> {

const oneDay = 60 * 60 * 24;

async function handleGet(request: Request): Promise<Response> {
const rscAppStream = createRscAppStream({
...createRscAppOptions({requestUrl: request.url}),
reactClientManifest,
mainCssHref: cssManifest[`main.css`]!,
});
// eslint-disable-next-line @typescript-eslint/promise-function-async
function handleGet(request: Request): Promise<Response> {
const {pathname, search} = new URL(request.url);

return routerLocationAsyncLocalStorage.run({pathname, search}, async () => {
const rscAppStream = createRscAppStream({
app: createRscApp(),
reactClientManifest,
mainCssHref: cssManifest[`main.css`]!,
});

if (request.headers.get(`accept`) === `text/x-component`) {
return new Response(rscAppStream, {
headers: {
'Content-Type': `text/x-component; charset=utf-8`,
'Cache-Control': `s-maxage=60, stale-while-revalidate=${oneDay}`,
},
});
}

if (request.headers.get(`accept`) === `text/x-component`) {
return new Response(rscAppStream, {
const htmlStream = await createHtmlStream(rscAppStream, {
reactSsrManifest,
bootstrapScripts: [jsManifest[`main.js`]!],
});

return new Response(htmlStream, {
headers: {
'Content-Type': `text/x-component; charset=utf-8`,
'Content-Type': `text/html; charset=utf-8`,
'Cache-Control': `s-maxage=60, stale-while-revalidate=${oneDay}`,
},
});
}

const htmlStream = await createHtmlStream(rscAppStream, {
reactSsrManifest,
bootstrapScripts: [jsManifest[`main.js`]!],
});

return new Response(htmlStream, {
headers: {
'Content-Type': `text/html; charset=utf-8`,
'Cache-Control': `s-maxage=60, stale-while-revalidate=${oneDay}`,
},
});
}

Expand Down
11 changes: 8 additions & 3 deletions apps/vercel-app/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,19 +122,24 @@ export default function createConfigs(_env, argv) {
},
resolve: {
plugins: [new ResolveTypeScriptPlugin()],
conditionNames: [`workerd`, `...`],
conditionNames: [`workerd`, `node`, `...`],
},
module: {
rules: [
{
resource: (value) =>
/core\/lib\/server\/rsc\.js$/.test(value) ||
/create-rsc-app-options\.tsx$/.test(value),
/create-rsc-app\.tsx$/.test(value),
layer: webpackRscLayerName,
},
{
// AsyncLocalStorage module instances must be in a shared layer.
layer: `shared`,
test: /(router-location-async-local-storage|core\/lib\/server\/use-router-location\.js)/,
},
{
issuerLayer: webpackRscLayerName,
resolve: {conditionNames: [`react-server`, `...`]},
resolve: {conditionNames: [`react-server`, `node`, `...`]},
},
{
oneOf: [
Expand Down
Loading