Skip to content

Commit

Permalink
♻️ use a context provider for the rsc cache
Browse files Browse the repository at this point in the history
  • Loading branch information
Fredkiss3 committed Dec 17, 2023
1 parent f6c600f commit 41d2092
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 57 deletions.
26 changes: 26 additions & 0 deletions src/app/(components)/cache/cache-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";
import * as React from "react";

export function RSCCacheProvider({ children }: { children: React.ReactNode }) {
const [cache] = React.useState(() => new Map());
return (
<CacheContext.Provider value={cache}>{children}</CacheContext.Provider>
);
}

export type RSCCacheContextType = Map<
string,
Promise<React.JSX.Element>
> | null;

export const CacheContext = React.createContext<RSCCacheContextType>(null);

export function useRSCCacheContext() {
const cache = React.use(CacheContext);

if (!cache) {
throw new Error("Cache is null, this should never arrives");
}

return cache;
}
74 changes: 31 additions & 43 deletions src/app/(components)/cache/cache.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as RSDW from "react-server-dom-webpack/client";

import { getSSRManifest } from "~/app/(components)/cache/manifest";
import { ErrorBoundary } from "react-error-boundary";
import { useRSCCacheContext } from "~/app/(components)/cache/cache-provider";

function transformStringToStream(input: string) {
// Using Flight to deserialize the args from the string.
Expand All @@ -21,15 +22,43 @@ function transformStringToStream(input: string) {
});
}

export function CacheClient({ payload }: { payload: string }) {
export function CacheClient({
payload,
cacheKey
}: {
payload: string;
cacheKey: string;
}) {
if (typeof window === "undefined") {
console.time("running cache client for SSR...");
console.log("[SSR] before `use`");
} else {
console.log("[CSR] before `use`");
console.time("running cache client for CSR...");
}
const element = React.use(resolveElementCached(payload));

let rscPromise: Promise<React.JSX.Element> | null = null;
const rscCache = useRSCCacheContext();
if (rscCache.has(cacheKey)) {
rscPromise = rscCache.get(cacheKey)!;
} else {
const rscStream = transformStringToStream(payload);
// Render to HTML
if (typeof window === "undefined") {
// the SSR manifest contains all the client components that will be SSR'ed
// And also how to import them
rscPromise = RSDWSSr.createFromReadableStream(
rscStream,
getSSRManifest()
);
} else {
// Hydrate or CSR
rscPromise = RSDW.createFromReadableStream(rscStream, {});
}
rscCache.set(cacheKey, rscPromise);
}

const element = React.use(rscPromise);
if (typeof window === "undefined") {
console.log("[SSR] after `use`");
console.timeEnd("running cache client for SSR...");
Expand All @@ -40,47 +69,6 @@ export function CacheClient({ payload }: { payload: string }) {
return element;
}

/**
* Custom `cache` function as `React.cache` doesn't work in the client
* @param fn
* @returns
*/
function fnCache<T extends (...args: any[]) => any>(fn: T): T {
const cache = new Map<string, any>();

return function cachedFn(...args: Parameters<T>): ReturnType<T> {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}

const result = fn(...args);
cache.set(key, result);
return result;
} as T;
}

const resolveElementCached = fnCache(async function resolveElementCached(
payload: string
) {
const rscStream = transformStringToStream(payload);
let rscPromise: Promise<React.JSX.Element> | null = null;

// Render to HTML
if (typeof window === "undefined") {
// the SSR manifest contains all the client components that will be SSR'ed
// And also how to import them
rscPromise = RSDWSSr.createFromReadableStream(rscStream, getSSRManifest());
}

// Hydrate or CSR
if (rscPromise === null) {
rscPromise = RSDW.createFromReadableStream(rscStream, {});
}

return await rscPromise;
});

export function CacheErrorBoundary({
children,
fallback
Expand Down
6 changes: 4 additions & 2 deletions src/app/(components)/cache/cache.server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,14 @@ export async function Cache({
}

if (debug) {
return <pre>{cachedPayload.rsc}</pre>;
return (
<pre className="max-w-full overflow-scroll">{cachedPayload.rsc}</pre>
);
}

return (
<CacheErrorBoundary fallback={ssrErrorFallback ?? <></>}>
<CacheClient payload={cachedPayload.rsc} />
<CacheClient payload={cachedPayload.rsc} cacheKey={fullKey} />
</CacheErrorBoundary>
);
} catch (error) {
Expand Down
4 changes: 3 additions & 1 deletion src/app/(components)/markdown/markdown-h.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export function MarkdownH({ as, showLink, ...props }: MarkdownHProps) {
href={`#${props.id}`}
className={clsx(
"absolute -left-6 -top-1.5 opacity-100 transition duration-150",
"md:opacity-0 md:group-hover:opacity-100"
"md:opacity-0 md:group-hover:opacity-100",
"focus:opacity-100 focus:ring-2 focus:ring-accent focus:outline-none",
"rounded-md"
)}
>
<LinkIcon className="h-5 w-5" />
Expand Down
5 changes: 4 additions & 1 deletion src/app/(components)/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as React from "react";
// components
import { RouterProvider } from "react-aria-components";
import { ReactQueryProvider } from "~/app/(components)/react-query-provider";
import { RSCCacheProvider } from "~/app/(components)/cache/cache-provider";

// utils
import { useRouter } from "next/navigation";
Expand All @@ -13,7 +14,9 @@ export function ClientProviders({ children }: { children: React.ReactNode }) {

return (
<RouterProvider navigate={router.push}>
<ReactQueryProvider>{children}</ReactQueryProvider>
<RSCCacheProvider>
<ReactQueryProvider>{children}</ReactQueryProvider>
</RSCCacheProvider>
</RouterProvider>
);
}
19 changes: 9 additions & 10 deletions src/lib/server/rsc-utils.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export function nextCache<T extends Callback>(
return cache(unstable_cache(cb, options.tags, options));
}

/**
* This function is only used in `DEV` because fetch-cache is bypassed by nextjs on DEV
*/
function cacheForDev<T extends Callback>(
cb: T,
options: {
Expand All @@ -30,16 +33,12 @@ function cacheForDev<T extends Callback>(
cached: ReturnType<T>;
}>(key);

if (!cachedValue) {
cachedValue = await cb(...args);
await kv.set(
key,
{
cached: cachedValue
},
options.revalidate
);
if (!cachedValue?.cached) {
cachedValue = {
cached: await cb(...args)
};
await kv.set(key, cachedValue, options.revalidate);
}
return cachedValue!.cached;
return cachedValue.cached;
};
}

0 comments on commit 41d2092

Please sign in to comment.