diff --git a/packages/gitbook/src/components/DocumentView/CodeBlock/ClientCodeBlock.tsx b/packages/gitbook/src/components/DocumentView/CodeBlock/ClientCodeBlock.tsx index 30315de29..fb3e40ae6 100644 --- a/packages/gitbook/src/components/DocumentView/CodeBlock/ClientCodeBlock.tsx +++ b/packages/gitbook/src/components/DocumentView/CodeBlock/ClientCodeBlock.tsx @@ -6,11 +6,10 @@ import { useDebounceCallback } from 'usehooks-ts'; import type { CustomizationThemedCodeTheme, DocumentBlockCode } from '@gitbook/api'; import type { BlockProps } from '../Block'; -import { type InlineExpressionVariables, useEvaluateInlineExpression } from '../InlineExpression'; +import type { InlineExpressionVariables } from '../InlineExpression/types'; import { CodeBlockRenderer } from './CodeBlockRenderer'; import type { HighlightTheme, RenderedInline } from './highlight-tokens'; import { plainHighlight } from './plain-highlight'; -import { useAdaptiveVisitor } from '@/components/Adaptive'; import { useInViewportListener } from '@/components/hooks/useInViewportListener'; import { useScrollListener } from '@/components/hooks/useScrollListener'; import { Button, ToggleChevron } from '@/components/primitives'; @@ -20,6 +19,9 @@ import { type ClassValue, tcls } from '@/lib/tailwind'; export type ClientBlockProps = Pick, 'block' | 'style'> & { inlines: RenderedInline[]; inlineExprVariables: InlineExpressionVariables; + /** Only supplied for blocks that contain one: evaluating needs `@gitbook/expr`, which is a + * quarter of a megabyte of JS parser we keep out of the bundle otherwise. */ + evaluateInlineExpression?: (expression: string) => string; mode: BlockProps['context']['mode']; themes?: CustomizationThemedCodeTheme; embedded?: boolean; @@ -32,17 +34,11 @@ export const CODE_BLOCK_DEFAULT_COLLAPSED_LINE_COUNT = 10; * It allows us to defer some load to avoid blocking the rendering of the whole page with block highlighting. */ export function ClientCodeBlock(props: ClientBlockProps) { - const { block, mode, style, inlines, inlineExprVariables, themes, embedded } = props; + const { block, mode, style, inlines, evaluateInlineExpression, themes, embedded } = props; const blockRef = useRef(null); const isInViewportRef = useRef(false); const [isInViewport, setIsInViewport] = useState(false); - const getAdaptiveVisitorClaims = useAdaptiveVisitor(); - const visitorClaims = getAdaptiveVisitorClaims(); - const evaluateInlineExpression = useEvaluateInlineExpression({ - visitorClaims, - variables: inlineExprVariables, - }); const plainTheme = useMemo( () => plainHighlight(block, inlines, { evaluateInlineExpression, themes }), [block, inlines, evaluateInlineExpression, themes] diff --git a/packages/gitbook/src/components/DocumentView/CodeBlock/ClientCodeBlockWithExpressions.tsx b/packages/gitbook/src/components/DocumentView/CodeBlock/ClientCodeBlockWithExpressions.tsx new file mode 100644 index 000000000..33d4f8290 --- /dev/null +++ b/packages/gitbook/src/components/DocumentView/CodeBlock/ClientCodeBlockWithExpressions.tsx @@ -0,0 +1,19 @@ +'use client'; + +import { ClientCodeBlock, type ClientBlockProps } from './ClientCodeBlock'; +import { useAdaptiveVisitor } from '@/components/Adaptive'; +import { useEvaluateInlineExpression } from '@/components/DocumentView/InlineExpression/useEvaluateInlineExpression'; + +/** + * Code block whose content contains inline expressions, so it needs the expression runtime. + * Kept in its own module: importing it pulls `@gitbook/expr` and its JS parser. + */ +export function ClientCodeBlockWithExpressions(props: ClientBlockProps) { + const getAdaptiveVisitorClaims = useAdaptiveVisitor(); + const evaluateInlineExpression = useEvaluateInlineExpression({ + visitorClaims: getAdaptiveVisitorClaims(), + variables: props.inlineExprVariables, + }); + + return ; +} diff --git a/packages/gitbook/src/components/DocumentView/CodeBlock/ClientCodeBlockWithExpressionsLazy.tsx b/packages/gitbook/src/components/DocumentView/CodeBlock/ClientCodeBlockWithExpressionsLazy.tsx new file mode 100644 index 000000000..42d564559 --- /dev/null +++ b/packages/gitbook/src/components/DocumentView/CodeBlock/ClientCodeBlockWithExpressionsLazy.tsx @@ -0,0 +1,19 @@ +'use client'; + +import dynamic from 'next/dynamic'; + +import type { ClientBlockProps } from './ClientCodeBlock'; + +// `ssr: true` keeps the evaluated code in the server-rendered HTML; the point of the boundary is to +// move `@gitbook/expr` (~254 KB with its parser) out of the route's eager entry. +const ClientCodeBlockWithExpressions = dynamic( + () => + import('./ClientCodeBlockWithExpressions').then( + (mod) => mod.ClientCodeBlockWithExpressions + ), + { ssr: true } +); + +export function ClientCodeBlockWithExpressionsLazy(props: ClientBlockProps) { + return ; +} diff --git a/packages/gitbook/src/components/DocumentView/CodeBlock/CodeBlock.tsx b/packages/gitbook/src/components/DocumentView/CodeBlock/CodeBlock.tsx index 0bae7b222..fe9303a44 100644 --- a/packages/gitbook/src/components/DocumentView/CodeBlock/CodeBlock.tsx +++ b/packages/gitbook/src/components/DocumentView/CodeBlock/CodeBlock.tsx @@ -9,6 +9,7 @@ import type { import type { BlockProps } from '../Block'; import { Blocks } from '../Blocks'; import { ClientCodeBlock } from './ClientCodeBlock'; +import { ClientCodeBlockWithExpressionsLazy } from './ClientCodeBlockWithExpressionsLazy'; import { CodeBlockRenderer } from './CodeBlockRenderer'; import { highlight } from './highlight'; import { type RenderedInline, getInlines } from './highlight-tokens'; @@ -121,8 +122,11 @@ export async function CodeBlock( {isMermaid ? ( + ) : hasInlineExpression ? ( + ) : ( )} diff --git a/packages/gitbook/src/components/DocumentView/CodeBlock/MermaidCodeBlock.tsx b/packages/gitbook/src/components/DocumentView/CodeBlock/MermaidCodeBlock.tsx index 902370d52..b74d87ca4 100644 --- a/packages/gitbook/src/components/DocumentView/CodeBlock/MermaidCodeBlock.tsx +++ b/packages/gitbook/src/components/DocumentView/CodeBlock/MermaidCodeBlock.tsx @@ -8,6 +8,7 @@ import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useSta import { createPortal } from 'react-dom'; import { type ClientBlockProps, ClientCodeBlock } from './ClientCodeBlock'; +import { ClientCodeBlockWithExpressions } from './ClientCodeBlockWithExpressions'; import { getPlainCodeBlock } from './highlight-tokens'; import { MermaidPanZoomControls } from './MermaidPanZoomControls'; import { useHasBeenInViewport } from '@/components/hooks/useHasBeenInViewport'; @@ -20,6 +21,7 @@ import { tcls } from '@/lib/tailwind'; export function MermaidCodeBlock( props: ClientBlockProps & { mermaidRuntimeURL: string; + hasInlineExpression: boolean; } ) { const { block, mode, style, mermaidRuntimeURL } = props; @@ -169,7 +171,11 @@ export function MermaidCodeBlock( }, []); if (error) { - return ; + return props.hasInlineExpression ? ( + + ) : ( + + ); } // The live diagram subtree. It is portaled into a stable host that moves between the diff --git a/packages/gitbook/src/components/DocumentView/CodeBlock/MermaidCodeBlockLazy.tsx b/packages/gitbook/src/components/DocumentView/CodeBlock/MermaidCodeBlockLazy.tsx index 26a9af3b0..d8bdfdc2d 100644 --- a/packages/gitbook/src/components/DocumentView/CodeBlock/MermaidCodeBlockLazy.tsx +++ b/packages/gitbook/src/components/DocumentView/CodeBlock/MermaidCodeBlockLazy.tsx @@ -11,6 +11,8 @@ const MermaidCodeBlock = dynamic( { ssr: true } ); -export function MermaidCodeBlockLazy(props: ClientBlockProps & { mermaidRuntimeURL: string }) { +export function MermaidCodeBlockLazy( + props: ClientBlockProps & { mermaidRuntimeURL: string; hasInlineExpression: boolean } +) { return ; } diff --git a/packages/gitbook/src/components/DocumentView/InlineExpression/InlineExpression.tsx b/packages/gitbook/src/components/DocumentView/InlineExpression/InlineExpression.tsx index b548ff063..9a756d8cf 100644 --- a/packages/gitbook/src/components/DocumentView/InlineExpression/InlineExpression.tsx +++ b/packages/gitbook/src/components/DocumentView/InlineExpression/InlineExpression.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import type { DocumentInlineExpression } from '@gitbook/api'; import type { InlineProps } from '../Inline'; -import { InlineExpressionValue } from './InlineExpressionValue'; +import { InlineExpressionValueLazy } from './InlineExpressionValueLazy'; /** * Render an inline expression. @@ -25,7 +25,7 @@ export function InlineExpression(props: InlineProps) { return ( - + ); } diff --git a/packages/gitbook/src/components/DocumentView/InlineExpression/InlineExpressionValueLazy.tsx b/packages/gitbook/src/components/DocumentView/InlineExpression/InlineExpressionValueLazy.tsx new file mode 100644 index 000000000..dbcb5f424 --- /dev/null +++ b/packages/gitbook/src/components/DocumentView/InlineExpression/InlineExpressionValueLazy.tsx @@ -0,0 +1,19 @@ +'use client'; + +import dynamic from 'next/dynamic'; + +import type { InlineExpressionVariables } from './types'; + +// `ssr: true` keeps the evaluated value in the server-rendered HTML; the boundary exists to move +// `@gitbook/expr` and its JS parser out of the route's eager entry. +const InlineExpressionValue = dynamic( + () => import('./InlineExpressionValue').then((mod) => mod.InlineExpressionValue), + { ssr: true } +); + +export function InlineExpressionValueLazy(props: { + expression: string; + variables: InlineExpressionVariables; +}) { + return ; +} diff --git a/packages/gitbook/src/components/DocumentView/InlineExpression/index.ts b/packages/gitbook/src/components/DocumentView/InlineExpression/index.ts index 59dec96a7..79ce6d991 100644 --- a/packages/gitbook/src/components/DocumentView/InlineExpression/index.ts +++ b/packages/gitbook/src/components/DocumentView/InlineExpression/index.ts @@ -1,3 +1,2 @@ export * from './InlineExpression'; export * from './types'; -export * from './useEvaluateInlineExpression';