Skip to content

Highlight code against single theme when mode is resolved - #259

Open
benvinegar wants to merge 1 commit into
mainfrom
claude/shiki-single-theme-render
Open

Highlight code against single theme when mode is resolved#259
benvinegar wants to merge 1 commit into
mainfrom
claude/shiki-single-theme-render

Conversation

@benvinegar

Copy link
Copy Markdown
Member

Summary

Optimize code highlighting performance by tokenizing against a single theme when the color scheme is already known, rather than always tokenizing against both light and dark themes and discarding one with CSS.

Key Changes

  • Refactored shikiPair()shikiThemeOptions(): Now accepts both theme and mode parameters. When mode is resolved ("light" or "dark"), returns a single-theme option; when mode is undefined, returns the light/dark pair for unpinned loads that follow the OS via media query.

  • Updated shikiSchemeCss(): Simplified logic to return empty string for any resolved mode (since colors are already correct inline), and only emit the media query when mode is undefined.

  • Type safety: Introduced ShikiThemeOptions union type to distinguish between single-theme ({ theme: string }) and dual-theme ({ themes: { light: string; dark: string } }) configurations.

  • Updated call sites: renderMarkdown() and renderCode() now pass opts.mode to shikiThemeOptions() and use the resulting options with shiki's codeToHtml().

Performance Impact

  • 1400-line code surface: 1.14s → 543ms rendering time; document size 751KB → 529KB
  • Markdown with fenced code: Similar proportional improvements
  • Unpinned loads (/s/:id opened outside the viewer): Still receive both themes and follow OS via media query, no regression

Implementation Details

The viewer always appends &mode= to surface iframe sources, so it can tell the server which scheme it resolved. This change leverages that signal: when mode is known, shiki only tokenizes once instead of twice, eliminating wasted work that was being discarded by CSS. Only direct unpinned loads need both themes, and they continue to work as before.

https://claude.ai/code/session_01MGMuudU8anVrxB7sjY9hpt

shiki tokenizes once PER THEME, so asking for a light/dark pair costs exactly
twice as much as asking for one. We asked for both on every render and then
threw one away: shikiSchemeCss flipped to the dark colors with CSS, and the
viewer had already told us which scheme it resolved — Card.tsx appends &mode= to
every surface iframe src.

Highlight against the single resolved theme when mode is pinned, and drop the
now-unnecessary flip rule. An unpinned direct load of /s/:id (someone opening
the link outside the viewer) still gets both themes and follows the OS through
the existing media query.

Measured with the benchmark suite, 1402-line TypeScript surface:

  code/large          1.14 s  ->  543 ms   (-52%)
  code/large document 751 KB  ->  529 KB   (-30%)
  markdown/large      51.9 ms ->  27.9 ms  (-45%)
  theme switch burst  99.9 ms ->  53.8 ms  (-45%)

Through the real route (/s/:id?mode=dark): 1109 ms -> 582 ms.

Output is visually identical: dual-theme emitted the light color inline plus a
--shiki-dark custom property and overrode it with `color: var(--shiki-dark)
!important`; single-theme emits the same final color inline. The token color
sequences are identical, and the pre background resolves to the same value
either way. Terminal, diff, html and mermaid surfaces are untouched — none of
them take the dual-theme path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MGMuudU8anVrxB7sjY9hpt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants