Skip to content

Commit afed832

Browse files
committed
Protect against many renders
1 parent 4223bd0 commit afed832

File tree

10 files changed

+49
-18
lines changed

10 files changed

+49
-18
lines changed

src/app/(space)/(core)/~gitbook/pdf/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { notFound } from 'next/navigation';
1111
import * as React from 'react';
1212

1313
import { getContentPointer } from '@/app/(space)/fetch';
14-
import { DocumentView } from '@/components/DocumentView';
14+
import { DocumentView, createHighlightingContext } from '@/components/DocumentView';
1515
import { TrademarkLink } from '@/components/TableOfContents/Trademark';
1616
import { PolymorphicComponentProp } from '@/components/utils/types';
1717
import { getSpaceLanguage } from '@/intl/server';
@@ -216,6 +216,7 @@ async function PDFPageDocument(props: {
216216
const { space, page, refContext } = props;
217217

218218
const document = page.documentId ? await getDocument(space.id, page.documentId) : null;
219+
const shouldHighlightCode = createHighlightingContext();
219220

220221
return (
221222
<PrintPage id={pagePDFContainerId(page)}>
@@ -238,6 +239,7 @@ async function PDFPageDocument(props: {
238239
contentRefContext: refContext,
239240
resolveContentRef: (ref) => resolveContentRef(ref, refContext),
240241
getId: (id) => pagePDFContainerId(page, id),
242+
shouldHighlightCode,
241243
}}
242244
/>
243245
) : null}

src/components/DocumentView/CodeBlock/CodeBlock.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { DocumentBlockCode, JSONDocument } from '@gitbook/api';
33
import { tcls } from '@/lib/tailwind';
44

55
import { CopyCodeButton } from './CopyCodeButton';
6-
import { highlight, HighlightLine, HighlightToken } from './highlight';
6+
import { highlight, HighlightLine, HighlightToken, plainHighlighting } from './highlight';
77
import { BlockProps } from '../Block';
88
import { DocumentContext } from '../DocumentView';
99
import { Inline } from '../Inline';
@@ -15,7 +15,8 @@ import './theme.css';
1515
*/
1616
export async function CodeBlock(props: BlockProps<DocumentBlockCode>) {
1717
const { block, document, style, context } = props;
18-
const lines = await highlight(block);
18+
const withHighlighting = context.shouldHighlightCode();
19+
const lines = withHighlighting ? await highlight(block) : plainHighlighting(block);
1920

2021
const id = block.key!;
2122

src/components/DocumentView/CodeBlock/PlainCodeBlock.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export function PlainCodeBlock(props: { code: string; syntax: string }) {
5050
mode: 'default',
5151
contentRefContext: null,
5252
resolveContentRef: async () => null,
53+
shouldHighlightCode: () => true,
5354
}}
5455
block={block}
5556
ancestorBlocks={[]}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const CODE_HIGHLIGHT_BLOCK_LIMIT = 50;
2+
3+
/**
4+
* Protect against memory issues when highlighting a large number of code blocks.
5+
* This context only allows 50 code blocks per render to be highlighted.
6+
* Once highlighting can scale up to a large number of code blocks, it can be removed.
7+
*
8+
* https://linear.app/gitbook-x/issue/RND-3588/gitbook-open-code-syntax-highlighting-runs-out-of-memory-after-a
9+
*/
10+
export function createHighlightingContext() {
11+
let count = 0;
12+
return () => {
13+
count += 1;
14+
return count < CODE_HIGHLIGHT_BLOCK_LIMIT;
15+
};
16+
}

src/components/DocumentView/CodeBlock/highlight.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { asyncMutexFunction, singleton } from '@/lib/async';
1515
import { getNodeText } from '@/lib/document';
1616
import { trace } from '@/lib/tracing';
1717

18+
import { DocumentContext } from '../DocumentView';
19+
1820
export type HighlightLine = {
1921
highlighted: boolean;
2022
tokens: HighlightToken[];
@@ -29,14 +31,6 @@ type InlineIndexed = { inline: any; start: number; end: number };
2931

3032
type PositionedToken = ThemedToken & { start: number; end: number };
3133

32-
/**
33-
* Due to a combination of memory limitations of Cloudflare workers and the memory
34-
* cost of shiki, we need to set a limit on the number of blocks we can highlight
35-
* in a single page.
36-
*/
37-
let blockCount = 0;
38-
const BLOCK_LIMIT = 50;
39-
4034
/**
4135
* Highlight a code block while preserving inline elements.
4236
*/
@@ -47,12 +41,7 @@ export async function highlight(block: DocumentBlockCode): Promise<HighlightLine
4741
// Language not found, fallback to plain highlighting
4842
return plainHighlighting(block);
4943
}
50-
51-
blockCount++;
52-
if (blockCount > BLOCK_LIMIT) {
53-
// Too many blocks and we risk crashing the worker, fallback to plain highlighting
54-
return plainHighlighting(block);
55-
}
44+
debugger;
5645

5746
const inlines: InlineIndexed[] = [];
5847
const code = getPlainCodeBlock(block, inlines);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './CodeBlock';
22
export * from './PlainCodeBlock';
3+
export * from './createHighlightingContext';

src/components/DocumentView/DocumentView.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ export interface DocumentContext {
3636
* Transform an ID to be added to the DOM.
3737
*/
3838
getId?: (id: string) => string;
39+
40+
/**
41+
* Returns true if the given code block should be highlighted.
42+
* This function was added to protect against memory issues when highlighting
43+
* a large number of code blocks.
44+
* Once highlighting can scale up to a large number of code blocks, it can be removed.
45+
*
46+
* https://linear.app/gitbook-x/issue/RND-3588/gitbook-open-code-syntax-highlighting-runs-out-of-memory-after-a
47+
*/
48+
shouldHighlightCode: () => boolean;
3949
}
4050

4151
export interface DocumentContextProps {

src/components/DocumentView/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './DocumentView';
2+
export { createHighlightingContext } from './CodeBlock';

src/components/PageBody/PageBody.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,17 @@ import { PageCover } from './PageCover';
1313
import { PageFooterNavigation } from './PageFooterNavigation';
1414
import { PageHeader } from './PageHeader';
1515
import { TrackPageView } from './TrackPageView';
16-
import { DocumentView } from '../DocumentView';
16+
import { DocumentView, createHighlightingContext } from '../DocumentView';
1717
import { PageFeedbackForm } from '../PageFeedback';
1818
import { DateRelative } from '../primitives';
1919

20+
/**
21+
* Due to a combination of memory limitations of Cloudflare workers and the memory
22+
* cost of shiki, we need to set a limit on the number of blocks we can highlight
23+
* in a single page.
24+
*/
25+
const CODE_HIGHLIGHT_BLOCK_LIMIT = 50;
26+
2027
export function PageBody(props: {
2128
space: Space;
2229
contentTarget: ContentTarget;
@@ -32,6 +39,7 @@ export function PageBody(props: {
3239
const asFullWidth = document ? hasFullWidthBlock(document) : false;
3340
const language = getSpaceLanguage(customization);
3441
const updatedAt = page.updatedAt ?? page.createdAt;
42+
const shouldHighlightCode = createHighlightingContext();
3543

3644
return (
3745
<>
@@ -73,6 +81,7 @@ export function PageBody(props: {
7381
contentRefContext: context,
7482
resolveContentRef: (ref, options) =>
7583
resolveContentRef(ref, context, options),
84+
shouldHighlightCode,
7685
}}
7786
/>
7887
) : (

src/components/Search/server-actions.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ function transformAnswer(
161161
mode: 'default',
162162
contentRefContext: null,
163163
resolveContentRef: async () => null,
164+
shouldHighlightCode: () => false,
164165
}}
165166
style={['space-y-5']}
166167
/>

0 commit comments

Comments
 (0)