syntax-highlighter-bench
This is a benchmark of JS syntax highlighters: fuz_code, Twinkleplop, Prism, and Shiki with each of its two engines, run on the same inputs through one adapter each. It times throughput and cold start on one named machine, and measures bundle size, install footprint, retained memory, token counts, and output size, which come out the same on any machine with the same Node version.
The maintainer of this benchmark also maintains fuz_code, so that library is marked wherever it appears, every input is labeled by where it came from, and the method is published for anyone to reproduce.
The libraries have different features. Shiki highlights with the TextMate grammars and themes VS Code uses; Twinkleplop aims for a similar feature set, with its own themes, annotations, and Twoslash support; and both do more than fuz_code, which has the fewest features of the four: a single-pass lexer per language, with CSS classes for its theme. Their fidelity differs too: how finely each splits the same source, and how accurately it classifies each part. These numbers compare the work all of them share, not what one offers that another doesn't, and they time each library's output without scoring its quality.
Every result, with filters · how it is measured · the inputs · the adapters, and how to add a library
AI disclosure: this is an LLM-generated repo guided by a person.
Headline figures
Each bar's ratio is to the best library in the chart, where 1.00× is the best and a
ratio marked ≈ is inside the row's noise floor: the machine's for throughput, and
the heap check's tolerance for retained memory. Hover a bar to compare the others against it
instead.
- under 0.9×, better than the reference
- 0.9–1.1×, level
- 1.1–2×
- 2–5×
- 5–10×
- 10–100×
- 100× or more
Timed on laptop1 (AMD Ryzen 5 PRO 7530U with Radeon Graphics, Node v24.14.1) on
2026-10-11 at commit bd12c165af. The machine's noise floor is 2.3% for one cell and
0.3% for a geometric mean: a smaller difference is not a
result. See the method for what each number means.
The machine and the run
laptop1: AMD Ryzen 5 PRO 7530U with Radeon Graphics, 12 threads, performance governor, amd-pstate-epp driver, energy preference performance, boost on, 14.4 GB, kernel 6.12.90+deb13.1-amd64. Corpus e649d70d43. The anchor, a fixed workload read before and after measuring, drifted
2.2%: stable. Two processes measuring the same library on the same cell in this run were
4.0% apart at the 95th percentile and
0.9% at the median, against 4.1% at the 95th percentile in the calibration.
These are summaries. The results page has every cell behind them, on inputs from other sources too. The caveats say what the numbers don't: the libraries don't do identical work, and Shiki's HTML carries inline styles where the others emit classes.
What the numbers mean in practice
Whether a ratio matters depends on where the highlighting runs and how often. Which figure to read first:
- At build time, as a static site or documentation build highlights its pages once: throughput sets the build's time and nothing else. Readers get plain HTML, so the JS and the cold start never reach them, though a theme stylesheet still does.
- In the browser: everything a page loads and the cold start to a first highlight come first, then throughput on long pages.
- In an editor that highlights as you type: throughput at the size being edited, against the time of one frame.
- On a server that renders each request: throughput and retained memory, paid on every request and held by every process.
- In serverless and edge functions: the cold start, paid by every fresh instance, and the bundle where the platform limits its size.
| worked out for | fuz_codemaintainer's | Twinkleplop | Prism | Shiki (JS engine) | Shiki (wasm) |
|---|---|---|---|---|---|
| Highlighting to HTML, at the neutral geometric mean over 27 of 27 rows | |||||
| one 10 kB code block | 0.14 ms 1.00× | 0.29 ms 1.98× | 1.26 ms 8.73× | 11.7 ms 80.5× | 10.7 ms 73.6× |
| 10 kB blocks in one 16 ms frame whole blocks | 110 1.00× | 55 2.00× | 12 9.17× | 1 110× | 1 110× |
| 1 MB of source as a site build might highlight | 14.5 ms 1.00× | 28.7 ms 1.98× | 126 ms 8.73× | 1,167 ms 80.5× | 1,067 ms 73.6× |
What each language costs
| language | fuz_codemaintainer's | Twinkleplop | Prism | Shiki (JS engine) | Shiki (wasm) |
|---|---|---|---|---|---|
| The floor the library with no language | |||||
| core | 1.31 kB 1.00× | 16.4 kB 12.5× | 3.56 kB 2.71× | 56.4 kB 42.9× | 38.8 kB 29.6× |
| The first language the language alone, less the floor: with any runtime the floor leaves out | |||||
| TypeScript | 4.80 kB 2.26× | 27.7 kB 13.0× | 2.13 kB 1.00× | 15.6 kB 7.35× | 15.7 kB 7.36× |
| JS | 4.80 kB 2.68× | 24.9 kB 13.9× | 1.79 kB 1.00× | 16.1 kB 8.98× | 16.1 kB 9.00× |
| CSS | 1.19 kB 2.15× | 11.0 kB 19.9× | 552 B 1.00× | 11.8 kB 21.3× | 11.8 kB 21.4× |
| HTML | 7.76 kB 2.56× | 24.9 kB 8.20× | 3.04 kB 1.00× | 38.7 kB 12.8× | 39.0 kB 12.8× |
| XML not timed | 2.34 kB 2.47× | — unsupported: no xml | 949 B 1.00× | 4.94 kB 5.20× | 4.97 kB 5.24× |
| JSON | 695 B 3.26× | 606 B 2.85× | 213 B 1.00× | 610 B 2.86× | 615 B 2.89× |
| Svelte | 8.44 kB 2.48× | 25.9 kB 7.63× | 3.40 kB 1.00× | 46.6 kB 13.7× | 46.5 kB 13.7× |
| Markdown | 3.68 kB 1.59× | 2.31 kB 1.00× | 2.65 kB 1.15× | 5.55 kB 2.40× | 5.57 kB 2.41× |
| Bash | 3.36 kB 1.11× | 12.8 kB 4.20× | 3.04 kB 1.00× | 5.90 kB 1.94× | 5.93 kB 1.95× |
| Rust | 2.58 kB 2.32× | 13.7 kB 12.3× | 1.11 kB 1.00× | 2.55 kB 2.29× | 2.56 kB 2.30× |
| One more language every timed language, less all of them but this one, in minified bytes: near zero when the others already bring it in, as HTML and Svelte bring their script and style languages | |||||
| TypeScript | 0 B | 11.1 kB | 1.29 kB | 3 B | 3 B |
| JS | 0 B | 109 B | 0 B | 3 B | 3 B |
| CSS | 0 B | 95 B | 0 B | 3 B | 3 B |
| HTML | 88 B | 97 B | 0 B | 57.6 kB | 57.6 kB |
| JSON | 1.39 kB 3.09× | 1.03 kB 2.29× | 449 B 1.00× | 2.80 kB 6.23× | 2.80 kB 6.23× |
| Svelte | 2.39 kB 1.00× | 4.81 kB 2.02× | 2.55 kB 1.07× | 24.5 kB 10.2× | 24.5 kB 10.2× |
| Markdown | 4.38 kB 1.00× | 6.73 kB 1.54× | 5.24 kB 1.19× | 59.3 kB 13.5× | 59.3 kB 13.5× |
| Bash | 8.09 kB 1.32× | 7.67 kB 1.25× | 6.14 kB 1.00× | 41.5 kB 6.75× | 41.5 kB 6.75× |
| Rust | 6.29 kB 2.53× | 10.6 kB 4.28× | 2.49 kB 1.00× | 15.0 kB 6.05× | 15.0 kB 6.05× |
Coverage
| language | fuz_codemaintainer's | Twinkleplop | Prism | Shiki (JS engine) | Shiki (wasm) |
|---|---|---|---|---|---|
TypeScript ts | yes | yes | yes | yes | yes |
JS js | yes1 | yes | yes | yes | yes |
CSS css | yes | yes | yes | yes | yes |
HTML html | yes | yes | yes | yes | yes |
XML xml not timed | yes | — unsupported | yes2 | yes3 | yes3 |
JSON json | yes | yes | yes | yes | yes |
Svelte svelte | yes | yes | yes4 | yes | yes |
Markdown md | yes | yes | yes | yes | yes |
Bash bash | yes | yes | yes | yes | yes |
Rust rust | yes | yes | yes | yes | yes |
- the TypeScript lexer, which fuz_code registers for JS
- the markup grammar, which Prism also registers as xml
- the XML grammar embeds the Java grammar, which loads with it
- the third-party prism-svelte grammar
Unsupported means the library's adapter doesn't claim the language, and is never shown as a zero or counted in a mean. Languages and sets has the details.
Libraries
| library | version | HTML output | engine | default theme's color schemes | what is measured |
|---|---|---|---|---|---|
fuz_code fuz_code maintainer's this benchmark's maintainer also maintains it | @fuzdev/fuz_code 0.52.1 | classes | single-pass lexers | light + dark | Single-pass lexers. |
Twinkleplop twinkleplop | @twinkleplop/core 0.2.2 with
| classes | grammar VM | light + dark | The full pipeline: the grammar plus the language's reclassifier stack, as shipped. |
Prism prism | prismjs 1.30.0 with
| classes | regex grammars | light | The core plus one component file per grammar, without the autoloader or any plugin. |
Shiki (JS engine) shiki_js | shiki 4.4.3 | inline styles | TextMate grammars | light | The JS RegExp engine. The HTML carries inline styles rather than classes. |
Shiki (wasm) shiki_wasm | shiki 4.4.3 | inline styles | TextMate grammars | light | The Oniguruma wasm engine, Shiki's default. The HTML carries inline styles rather than classes. |
Each library is driven through one adapter with notes on the entry points it uses, so its maintainers can review or replace it. See the adapters.