import type { HighlighterCore } from 'shiki/core' import { createHighlighterCore } from 'shiki/core' import { createOnigurumaEngine } from 'shiki/engine/oniguruma' /** * Shiki is meant to be used as a singleton: each instance carries its own WASM * engine and grammar set. The source pane unmounts and remounts on every story * navigation, so creating one per mount grows memory for the lifetime of the * tab — and trips Shiki's own warning once ten are live. * * The langs and themes are fixed, so one shared instance is always correct. */ let highlighterPromise: Promise | undefined /** * `shiki/core`, not `shiki`: the latter is the full-bundle entry and ships every * grammar and theme whatever the options ask for — 10 MB a book (#304). The * dynamic imports are the interface, not a lazy-loading choice. */ export function getHighlighter(): Promise { highlighterPromise ??= createHighlighterCore({ langs: [ import('shiki/langs/html.mjs'), import('shiki/langs/jsx.mjs'), ], /* * `github-light-high-contrast`, not `github-light`. The plain one paints * keywords at 4.38:1 on the pane background, variables at 3.34 and tag * names at 4.43 — three of its twelve token colours miss AA (#533). This is * GitHub's own accessible build of the same theme, so code keeps reading the * way it did, and the colours are maintained upstream rather than darkened * by hand here. */ themes: [ import('shiki/themes/github-light-high-contrast.mjs'), import('shiki/themes/github-dark.mjs'), ], engine: createOnigurumaEngine(import('shiki/wasm')), }).catch((e) => { // Don't let a cached rejection disable highlighting for the rest of the // session — drop it so the next mount can try again. highlighterPromise = undefined throw e }) return highlighterPromise }