Files
Anthony Lio 6d08a747f1 fix(docs): guide reference perf enhancements (#50239)
## What kind of change does this PR introduce?

follow-up to #50235 to reduce reference page payloads and cold rendering
overhead

## What is the current behavior?

reference pages ship a large rsc payload inside the html _ most of it is
duplication rather than content along with shiki that writes ~30
character css variable name for every syntax token making the page heavy
in some cases

## What is the new behavior?

- moves repeated styles into shared css and uses compact, namespaced
token classes
- renders details icons inside the client trigger
- follows shiki’s guidance to [reuse one
highlighter](https://shiki.style/guide/best-performance#cache-the-highlighter-instance)
and [load languages on
demand](https://shiki.style/guide/best-performance#use-shorthands)

`page size`
page | before | after | change
-- | -- | -- | --
javascript | 10.61 mb | 8.28 mb | -21.9%
dart | 4.59 mb | 4.13 mb | -10.1%
python | 4.38 mb | 3.73 mb | -14.9%
swift | 2.87 mb | 2.56 mb | -10.9%
server | 1.59 mb | 1.35 mb | -15.2%
kotlin | 3.42 mb | 3.17 mb | -7.2%

`cold initialization`
language | before | after | reduction
-- | -- | -- | --
bash | 2,180 ms | 23 ms | 98.95%
javascript | 2,245 ms | 38 ms | 98.29%

## Additional context

measured on a local production build which uses the checked in generated
content _ production has larger sdk data, so absolute sizes there will
be higher

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added reusable expand/collapse controls for API reference details,
with updated icons, labels, and styling.
* Improved code block rendering with class-based syntax highlighting,
wrapped-code support, responsive layouts, and lazy language loading.

* **Style**
* Added theme-aware syntax-token colors, line-number styling, and
configurable code-block shadows.
  * Consolidated expandable reference panel and item styling.

* **Tests**
* Added coverage for syntax highlighting, code block rendering, language
support, token stability, and reference details.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-15 23:55:49 +03:00

157 lines
5.0 KiB
TypeScript

import { type PropsWithChildren } from 'react'
import { bundledLanguages, type BundledLanguage } from 'shiki'
import { createTwoslasher, type ExtraFiles, type NodeHover } from 'twoslash'
import { cn } from 'ui'
import { CodeBlockControls, CodeBlockTokens, type CodeToken } from './CodeBlock.client'
import { highlightCode } from './CodeBlock.highlight'
import { getCodeBlockLabel, getTokenClassName } from './CodeBlock.utils'
import denoTypes from './types/lib.deno.d.ts.include'
const extraFiles: ExtraFiles = { 'deno.d.ts': denoTypes }
const twoslasher = createTwoslasher({
extraFiles,
// todo: remove once Twoslash stops using deprecated baseUrl and node10 resolution
compilerOptions: { ignoreDeprecations: '6.0' },
})
const TWOSLASHABLE_LANGS: ReadonlyArray<string> = ['js', 'ts', 'javascript', 'typescript']
const BUNDLED_LANGUAGES = Object.keys(bundledLanguages)
export async function CodeBlock({
className,
lang: langSetting,
lineNumbers = true,
contents,
children,
skipTypeGeneration,
hideControls = false,
compact = false,
}: PropsWithChildren<{
className?: string
lang?: string
lineNumbers?: boolean
contents?: string
skipTypeGeneration?: boolean
hideControls?: boolean
compact?: boolean
}>) {
let code = (contents || extractCode(children)).trim()
const lang = tryToBundledLanguage(langSetting || '') || extractLang(children)
let twoslashed = null as null | Map<number, Map<number, Array<NodeHover>>>
if (!skipTypeGeneration && lang && TWOSLASHABLE_LANGS.includes(lang)) {
try {
const { code: editedCode, nodes } = twoslasher(code)
const hoverNodes: Array<NodeHover> = nodes.filter((node) => node.type === 'hover')
twoslashed = annotationsByLine(hoverNodes)
code = editedCode
} catch (_err) {
// Type compilation fails when imports aren't defined
}
}
const { tokens } = await highlightCode(code, lang)
return (
<div
className={cn(
'shiki',
'group',
'relative',
'not-prose',
'w-full',
compact ? 'border-0 my-0!' : 'border border-default rounded-lg shadow-codeblock',
'bg-200',
'text-sm',
className
)}
>
<div
className={cn(
'code-scroll',
'w-full overflow-x-auto overscroll-x-none',
!compact && 'rounded-lg',
'focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring'
)}
role="group"
aria-roledescription="code block"
aria-label={getCodeBlockLabel(lang, tokens.length)}
tabIndex={0}
>
<CodeBlockTokens
lineNumbers={lineNumbers}
lines={tokens.map((line, lineIndex) => {
let offset = 0
return line.map(({ content, color, fontStyle }): CodeToken => {
const annotations = twoslashed
?.get(lineIndex)
?.get(offset)
?.map(({ text, docs, tags }) => ({ text, docs, tags }))
offset += content.length
const className = getTokenClassName(color, fontStyle)
return annotations ? [content, className, annotations] : [content, className]
})
})}
/>
</div>
{/* After the code so the block is named before its controls, and outside the scroller so they stay pinned */}
{!hideControls && <CodeBlockControls content={code.trim()} />}
</div>
)
}
function extractCode(children: React.ReactNode): string {
if (typeof children === 'string') return children
const child = Array.isArray(children) ? children[0] : children
if (!!child && typeof child === 'object' && 'props' in child) {
const props = child.props
if (!!props && typeof props === 'object' && 'children' in props) {
const code = props.children
if (typeof code === 'string') return code
}
}
return ''
}
function extractLang(children: React.ReactNode): BundledLanguage | null {
if (typeof children === 'string') return null
const child = Array.isArray(children) ? children[0] : children
if (!!child && typeof child === 'object' && 'props' in child) {
const props = child.props
if (!!props && typeof props === 'object' && 'className' in props) {
const className = props.className
if (typeof className === 'string') {
const lang = className.split(' ').find((className) => className.startsWith('language-'))
return lang ? tryToBundledLanguage(lang.replace('language-', '')) : null
}
}
}
return null
}
function annotationsByLine(nodes: Array<NodeHover>): Map<number, Map<number, Array<NodeHover>>> {
const result = new Map()
nodes.forEach((node) => {
const line = node.line
const char = node.character
if (!result.has(line)) {
result.set(line, new Map())
}
if (!result.get(line).has(char)) {
result.get(line).set(char, [])
}
result.get(line).get(char).push(node)
})
return result
}
function tryToBundledLanguage(lang: string): BundledLanguage | null {
if (BUNDLED_LANGUAGES.includes(lang)) {
return lang as BundledLanguage
}
return null
}