Refactor content reference handling to use getContentRef method across components

This commit is contained in:
Nicolas Dorseuil
2025-06-16 09:47:21 +02:00
parent de19ada4fa
commit 7b4908c8fd
8 changed files with 85 additions and 54 deletions
+50 -31
View File
@@ -42,6 +42,8 @@ export interface PrefetchedPageData {
prefetchedRef: Promise<Map<ContentRef, Promise<ResolvedContentRef | null>>>;
}
const cachedInitialDate = cache(() => Date.now());
/**
* Fetches the page data matching the requested pathname and fallback to root page when page is not found.
*/
@@ -63,22 +65,29 @@ async function getPageDataWithFallback(args: {
export const getPrefetchedDataFromLayoutParams = cache(
(params: RouteLayoutParams): PrefetchedLayoutData => {
const staticSiteContext = getStaticSiteContext(params);
const startingDate = cachedInitialDate();
const staticSiteContext = getStaticSiteContext(params).finally(() => {
console.log(`Finished fetching static site context in ${Date.now() - startingDate}ms`);
});
const icons = Promise.all([
staticSiteContext.then(({ context }) => getIcon(context, 'light')),
staticSiteContext.then(({ context }) => getIcon(context, 'dark')),
]).then((urls) => [
{
url: urls[0],
type: 'image/png',
media: '(prefers-color-scheme: light)',
},
{
url: urls[1],
type: 'image/png',
media: '(prefers-color-scheme: dark)',
},
]);
])
.then((urls) => [
{
url: urls[0],
type: 'image/png',
media: '(prefers-color-scheme: light)',
},
{
url: urls[1],
type: 'image/png',
media: '(prefers-color-scheme: dark)',
},
])
.finally(() => {
console.log(`Finished fetching icons in ${Date.now() - startingDate}ms`);
});
return {
staticSiteContext,
@@ -112,33 +121,43 @@ export const prefetchedDocumentRef = (
if (document.nodes && Array.isArray(document.nodes)) {
traverseNodes(document.nodes);
}
console.log('Prefetched document references:', fetched.size);
return fetched;
};
export const getPrefetchedDataFromPageParams = cache((params: RouteParams): PrefetchedPageData => {
const startingDate = cachedInitialDate();
const { staticSiteContext } = getPrefetchedDataFromLayoutParams(params);
const pathname = getPagePathFromParams(params);
const pageData = staticSiteContext.then(({ context }) =>
getPageDataWithFallback({
context,
pagePathParams: {
pathname,
},
const pageData = staticSiteContext
.then(({ context }) =>
getPageDataWithFallback({
context,
pagePathParams: {
pathname,
},
})
)
.finally(() => {
console.log(`Finished fetching page data in ${Date.now() - startingDate}ms`);
});
const document = pageData
.then(({ context, pageTarget }) => {
if (!pageTarget?.page) {
return null;
}
return getPageDocument(context, pageTarget?.page);
})
);
const document = pageData.then(({ context, pageTarget }) => {
if (!pageTarget?.page) {
return null;
}
return getPageDocument(context, pageTarget?.page);
});
const prefetchedRef = Promise.all([staticSiteContext, document]).then(
([{ context }, document]) => {
.finally(() => {
console.log(`Finished fetching document in ${Date.now() - startingDate}ms`);
});
const prefetchedRef = Promise.all([staticSiteContext, document])
.then(([{ context }, document]) => {
// Prefetch the references in the document
return prefetchedDocumentRef(document, context);
}
);
})
.finally(() => {
console.log(`Finished prefetching references in ${Date.now() - startingDate}ms`);
});
return {
pageData,
document,
@@ -48,6 +48,7 @@ export function PlainCodeBlock(props: { code: string; syntax: string }) {
document={document}
context={{
mode: 'default',
getContentRef: async () => null, // No content references needed for plain code block
}}
block={block}
ancestorBlocks={[]}
@@ -2,7 +2,7 @@ import type { ClassValue } from '@/lib/tailwind';
import type { ContentRef, JSONDocument } from '@gitbook/api';
import type { GitBookAnyContext } from '@v2/lib/context';
import type { ResolvedContentRef } from '@/lib/references';
import type { ResolveContentRefOptions, ResolvedContentRef } from '@/lib/references';
import { BlockSkeleton } from './Block';
import { Blocks } from './Blocks';
@@ -30,7 +30,10 @@ export interface DocumentContext {
*/
wrapBlocksInSuspense?: boolean;
contentRef?: Promise<Map<ContentRef, Promise<ResolvedContentRef | null>>>;
getContentRef: (
ref: ContentRef,
options?: ResolveContentRefOptions
) => Promise<ResolvedContentRef | null>;
}
export interface DocumentContextProps {
@@ -1,6 +1,4 @@
import { type DocumentInlineLink, SiteInsightsLinkPosition } from '@gitbook/api';
import { resolveContentRef } from '@/lib/references';
import { Icon } from '@gitbook/icons';
import { StyledLink } from '../primitives';
import type { InlineProps } from './Inline';
@@ -10,16 +8,7 @@ import { Inlines } from './Inlines';
export async function InlineLink(props: InlineProps<DocumentInlineLink>) {
const { inline, document, context, ancestorInlines } = props;
const refs = await context.contentRef;
const resolved =
(await refs?.get(inline.data.ref)) ??
(context.contentContext
? await resolveContentRef(inline.data.ref, context.contentContext, {
// We don't want to resolve the anchor text here, as it can be very expensive and will block rendering if there is a lot of anchors link.
resolveAnchorText: false,
})
: null);
const resolved = await context.getContentRef(inline.data.ref);
if (!context.contentContext || !resolved) {
return (
@@ -6,7 +6,6 @@ import {
import { LinkBox, LinkOverlay } from '@/components/primitives';
import { Image } from '@/components/utils';
import { resolveContentRef } from '@/lib/references';
import { type ClassValue, tcls } from '@/lib/tailwind';
import { RecordColumnValue } from './RecordColumnValue';
@@ -28,12 +27,8 @@ export async function RecordCard(
: null;
const [cover, target] = await Promise.all([
coverFile && context.contentContext
? resolveContentRef({ kind: 'file', file: coverFile }, context.contentContext)
: null,
targetRef && context.contentContext
? resolveContentRef(targetRef, context.contentContext)
: null,
coverFile ? context.getContentRef({ kind: 'file', file: coverFile }) : null,
targetRef && context.getContentRef(targetRef),
]);
const coverIsSquareOrPortrait =
@@ -28,6 +28,7 @@ import { PageControlButtons } from './PageControlButtons';
import { PrintButton } from './PrintButton';
import './pdf.css';
import { sanitizeGitBookAppURL } from '@/lib/app';
import { resolveContentRef } from '@/lib/references';
import { getPageDocument } from '@v2/lib/data';
const DEFAULT_LIMIT = 100;
@@ -244,6 +245,8 @@ async function PDFPageDocument(props: {
...context,
page,
},
//TODO: Use prefetchedRef to avoid fetching the same content multiple times
getContentRef: (ref, options) => resolveContentRef(ref, context, options),
getId: (id) => getPagePDFContainerId(page, id),
}}
// We consider all pages as offscreen in PDF mode
@@ -6,7 +6,11 @@ import { getSpaceLanguage } from '@/intl/server';
import { t } from '@/intl/translate';
import { hasFullWidthBlock, isNodeEmpty } from '@/lib/document';
import type { AncestorRevisionPage } from '@/lib/pages';
import type { ResolvedContentRef } from '@/lib/references';
import {
type ResolveContentRefOptions,
type ResolvedContentRef,
resolveContentRef,
} from '@/lib/references';
import { tcls } from '@/lib/tailwind';
import { DocumentView, DocumentViewSkeleton } from '../DocumentView';
import { TrackPageViewEvent } from '../Insights';
@@ -35,6 +39,19 @@ export function PageBody(props: {
const language = getSpaceLanguage(customization);
const updatedAt = page.updatedAt ?? page.createdAt;
const getContentRef = async (
ref?: ContentRef,
options?: ResolveContentRefOptions
): Promise<ResolvedContentRef | null> => {
if (!ref) {
return null;
}
if (!options) {
return resolveContentRef(ref, context, options);
}
return props.prefetchedRef.then((prefetched) => prefetched.get(ref) ?? null);
};
return (
<>
<main
@@ -70,7 +87,7 @@ export function PageBody(props: {
context={{
mode: 'default',
contentContext: context,
contentRef: props.prefetchedRef,
getContentRef: getContentRef,
}}
/>
</React.Suspense>
@@ -20,6 +20,7 @@ import { createStreamableValue } from 'ai/rsc';
import type * as React from 'react';
import { joinPathWithBaseURL } from '@/lib/paths';
import { resolveContentRef } from '@/lib/references';
import { isV2 } from '@/lib/v2';
import type { IconName } from '@gitbook/icons';
import { throwIfDataError } from '@v2/lib/data';
@@ -345,6 +346,9 @@ async function transformAnswer(
mode: 'default',
contentContext: undefined,
wrapBlocksInSuspense: false,
// TODO: Use prefetched content references
getContentRef: async (ref, options) =>
resolveContentRef(ref, context, options),
}}
style={['space-y-5']}
/>