From 281952d27cb51faaa43a1f001d64026ede3e46cc Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Fri, 10 Jul 2026 22:04:56 -0400 Subject: [PATCH 01/15] docs: rework the library and make sh the default --- .changeset/plain-peas-cough.md | 5 + CLAUDE.md | 14 +- README.md | 14 +- src/lib/code_sample.ts | 2 +- src/lib/lexer_bash.ts | 12 +- src/lib/lexer_md.ts | 2 +- src/routes/+layout.svelte | 1 - src/routes/+page.svelte | 67 ++---- src/routes/CodeTome.svelte | 201 ------------------ src/routes/Footer.svelte | 7 - src/routes/about/+page.svelte | 37 ---- src/routes/benchmark/benchmark_fixtures.ts | 2 +- src/routes/docs/benchmark/+page.svelte | 16 -- src/routes/docs/library/+page.svelte | 17 -- src/routes/docs/samples/+page.svelte | 137 ++++++++++-- src/routes/docs/textarea/+page.svelte | 87 ++++++++ src/routes/docs/tomes.ts | 39 ++-- src/routes/docs/usage/+page.svelte | 195 ++++++++++++++++- src/routes/lang_color.ts | 19 ++ src/routes/samples/+page.svelte | 95 --------- src/routes/samples/all.gen.ts | 2 +- src/routes/samples/all.ts | 8 +- src/routes/style.css | 5 - src/routes/textarea/+page.svelte | 69 ------ .../bash_complex.html => sh/sh_complex.html} | 0 .../bash_complex.txt => sh/sh_complex.txt} | 0 src/test/fixtures/helpers.ts | 2 +- ...{sample_complex.bash => sample_complex.sh} | 0 src/test/lexer_bash.test.ts | 2 +- src/test/pathological.ts | 8 +- 30 files changed, 491 insertions(+), 574 deletions(-) create mode 100644 .changeset/plain-peas-cough.md delete mode 100644 src/routes/CodeTome.svelte delete mode 100644 src/routes/Footer.svelte delete mode 100644 src/routes/about/+page.svelte delete mode 100644 src/routes/docs/benchmark/+page.svelte delete mode 100644 src/routes/docs/library/+page.svelte create mode 100644 src/routes/docs/textarea/+page.svelte create mode 100644 src/routes/lang_color.ts delete mode 100644 src/routes/samples/+page.svelte delete mode 100644 src/routes/style.css delete mode 100644 src/routes/textarea/+page.svelte rename src/test/fixtures/generated/{bash/bash_complex.html => sh/sh_complex.html} (100%) rename src/test/fixtures/generated/{bash/bash_complex.txt => sh/sh_complex.txt} (100%) rename src/test/fixtures/samples/{sample_complex.bash => sample_complex.sh} (100%) diff --git a/.changeset/plain-peas-cough.md b/.changeset/plain-peas-cough.md new file mode 100644 index 00000000..8c3e3b81 --- /dev/null +++ b/.changeset/plain-peas-cough.md @@ -0,0 +1,5 @@ +--- +'@fuzdev/fuz_code': minor +--- + +refactor make `sh` the primary and `bash` the alias diff --git a/CLAUDE.md b/CLAUDE.md index a07283c3..1143fc45 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,10 +3,9 @@ > Syntax highlighting - hand-written single-pass lexers fuz_code (`@fuzdev/fuz_code`) is a runtime syntax highlighting library optimized -for HTML generation with CSS classes. It originated as a PrismJS fork and keeps -its `.token_*` class vocabulary, but the tokenizer is a full rewrite — one -hand-written single-pass lexer per language emitting a flat token event stream, -with zero regular expressions. +for HTML generation with CSS classes. It originated as a PrismJS fork, but the +tokenizer is a full rewrite — one hand-written single-pass lexer per language +emitting a flat token event stream, with zero regular expressions. For coding conventions, see Skill(fuz-stack). @@ -125,8 +124,9 @@ One `SyntaxLang` lexer per language, registered via `add_lang`: - `lexer_ts.ts` - TypeScript; also registers the `js`/`javascript` aliases (TS is a syntactic superset — there is no separate JS lexer) - `lexer_css.ts` - CSS (including native nesting) -- `lexer_bash.ts` - Bash; also registers the `sh`/`shell` aliases (POSIX sh - is a syntactic subset for highlighting — bash-family only, no fish etc.) +- `lexer_bash.ts` - the bash-family shell lexer, registered as `sh` with + `bash`/`shell` as aliases (POSIX sh is a syntactic subset for highlighting — + bash-family only, no fish etc.) - `lexer_markup.ts` - HTML (`markup`/`html`/`mathml`/`svg`: rawtext script/style/textarea/title, `style=`/`on*=` attribute embedding) and XML (`xml`/`ssml`/`atom`/`rss`: plain tag scanning), one shared scanner @@ -170,7 +170,7 @@ can't overflow the call stack; past the cap a region stays plain text. ## Supported languages -`ts`, `js`, `css`, `html`, `json`, `svelte`, `md`, `bash` +`ts`, `js`, `css`, `html`, `json`, `svelte`, `md`, `sh` ## Testing diff --git a/README.md b/README.md index a00dbb8d..b9186a62 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,9 @@ `fuz_code` is a runtime syntax highlighter: it turns source code into HTML with `.token_*` CSS classes, and knows nothing about the DOM. It originated as a fork -of [Prism](https://github.com/PrismJS/prism) ([prismjs.com](https://prismjs.com/)) -and keeps its `.token_*` class vocabulary, but the tokenizer is now a full -rewrite — one hand-written single-pass lexer per language emitting a flat token -event stream, with zero regular expressions. +of [Prism](https://github.com/PrismJS/prism) ([prismjs.com](https://prismjs.com/)), +but the tokenizer is now a full rewrite — one hand-written single-pass lexer per +language emitting a flat token event stream, with zero regular expressions. Highlights: @@ -111,7 +110,7 @@ Registered by default in `syntax_styler_global` — one hand-written lexer each: - [`ts`](src/lib/lexer_ts.ts) (TypeScript — also serves `js`/`javascript`, a syntactic subset) - [`css`](src/lib/lexer_css.ts) - [`json`](src/lib/lexer_json.ts) (with comments — jsonc) -- [`bash`](src/lib/lexer_bash.ts) (`sh`/`shell`) +- [`sh`](src/lib/lexer_bash.ts) (POSIX/bash family — also serves `bash`/`shell`) Add a language by writing a `SyntaxLang` lexer and registering it with `add_lang` — see the existing `lexer_*.ts` modules. @@ -123,7 +122,7 @@ Docs are a work in progress: - this readme has basic usage instructions - [CLAUDE.md](./CLAUDE.md) has more high-level docs including benchmarks - [code.fuz.dev](https://code.fuz.dev/) has usage examples with the Svelte component -- [samples](https://code.fuz.dev/samples) on the website +- [samples](https://code.fuz.dev/docs/samples) on the website (also see the [sample files](src/test/fixtures/samples/)) - [tests](src/test/) @@ -179,7 +178,6 @@ originally forked from [Prism](https://github.com/PrismJS/prism) with the Svelte support originally based on [`prism-svelte`](https://github.com/pngwn/prism-svelte) by [@pngwn](https://github.com/pngwn). The tokenizer has since been rewritten as -hand-written lexers, but the `.token_*` class vocabulary and the fork's lineage -remain. +hand-written lexers, but the fork's lineage remains. [MIT](LICENSE) diff --git a/src/lib/code_sample.ts b/src/lib/code_sample.ts index 065e130b..da240b1e 100644 --- a/src/lib/code_sample.ts +++ b/src/lib/code_sample.ts @@ -5,6 +5,6 @@ export interface CodeSample { } // Languages ordered from simple to complex -export const sample_langs = ['json', 'css', 'ts', 'html', 'svelte', 'md', 'bash'] as const; +export const sample_langs = ['json', 'css', 'ts', 'html', 'svelte', 'md', 'sh'] as const; export type SampleLang = (typeof sample_langs)[number]; diff --git a/src/lib/lexer_bash.ts b/src/lib/lexer_bash.ts index 4ebf5b2e..f3fdfaea 100644 --- a/src/lib/lexer_bash.ts +++ b/src/lib/lexer_bash.ts @@ -42,7 +42,7 @@ import { * unquoted heredoc bodies) runs on an explicit pooled frame stack, so * arbitrarily deep input tokenizes fully without touching the JS call stack. * - * Scope: the bash family — `sh`/`shell` alias this lexer. POSIX sh is a + * Scope: the bash family — registered as `sh`, with `bash`/`shell` as aliases. POSIX sh is a * syntactic subset of bash for highlighting purposes (everything sh scripts * use — `$(…)`, `$((…))`, backticks, heredocs, `${…}` — is shared syntax), * and bash-only forms don't occur in sh input. Shells with their own @@ -993,11 +993,11 @@ const lex_bash = (l: Lexer): void => { }; /** - * The Bash language registration for the lexer engine. + * The shell (bash-family) language registration for the lexer engine. * - * `sh` and `shell` alias this lexer: POSIX sh is a syntactic subset of bash - * for highlighting purposes, and the bash-only constructs this lexer - * additionally recognizes don't occur in sh input, so running it on sh + * Registered as `sh`; `bash` and `shell` alias it: POSIX sh is a syntactic + * subset of bash for highlighting purposes, and the bash-only constructs this + * lexer additionally recognizes don't occur in sh input, so running it on sh * scripts is exact. There is no separate sh lexer. */ -export const lexer_bash: SyntaxLang = {id: 'bash', aliases: ['sh', 'shell'], lex: lex_bash}; +export const lexer_bash: SyntaxLang = {id: 'sh', aliases: ['bash', 'shell'], lex: lex_bash}; diff --git a/src/lib/lexer_md.ts b/src/lib/lexer_md.ts index 2b687428..449cad94 100644 --- a/src/lib/lexer_md.ts +++ b/src/lib/lexer_md.ts @@ -94,7 +94,7 @@ add_fence_lang('css', 'css', T_LANG_CSS); add_fence_lang('html markup', 'markup', T_LANG_MARKUP); add_fence_lang('json', 'json', T_LANG_JSON); add_fence_lang('svelte', 'svelte', T_LANG_SVELTE); -add_fence_lang('bash sh shell', 'bash', T_LANG_BASH); +add_fence_lang('bash sh shell', 'sh', T_LANG_BASH); add_fence_lang('md markdown', 'md', T_LANG_MD); // line-internal whitespace — `is_space` from the toolkit includes newlines diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index dab63a08..9e955723 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,7 +1,6 @@ + + @fuzdev/fuz_code + +
-
+

fuz_code

-
- docs{#snippet icon()}{site.glyph}{/snippet} -

- fuz_code is a library for syntax highlighting. It can output strings of HTML and Svelte - components, and it has a preprocessor - for static compilation. It runs one hand-written lexer per language with zero regular expressions; - it originated as a fork of + fuz_code is a syntax highlighting library. It runs one hand-written lexer per language with + zero regular expressions, outputs HTML strings or Svelte components, and can compile static + content at build time. It originated as a fork of Prism - (prismjs.com) by - Lea Verou - and keeps its .token_* class vocabulary. More at the - docs. -

-

- To use fuz_css with Svelte, import the theme CSS file and Code component: -

- -

Svelte highlights by default:

- `} /> - -

Set lang for other languages, like TypeScript:

- `} /> -

Renders:

- -

Or CSS:

- `} /> -

Renders:

- -

- See the docs for more. + by Lea Verou.

-
- +
+ +
+
+ docs{#snippet icon()}{site.glyph}{/snippet} + samples{#snippet icon()}🎨{/snippet} + benchmark{#snippet icon()}⚡{/snippet}
diff --git a/src/routes/CodeTome.svelte b/src/routes/CodeTome.svelte deleted file mode 100644 index a86a7551..00000000 --- a/src/routes/CodeTome.svelte +++ /dev/null @@ -1,201 +0,0 @@ - - - - - -
-

- The - - Svelte component supports syntax styling with hand-written per-language lexers. fuz_code originated - as a fork of - Prism (prismjs.com) by - Lea Verou - and keeps its .token_* class vocabulary. -

-

To use it, import the default theme or your own:

- -

then use :

- \n\t// Something.svelte\n\timport Code from '@fuzdev/fuz_code/Code.svelte';\n\n\n`} - /> -

outputs:

- -
- - -

- By default fuz_code depends on fuz_css to provide - color-schema-aware color variables. If you're not using it, import - theme_variables.css or bring your own: -

- -
- - -

- The svelte_preprocess_fuz_code preprocessor compiles static - content at build time, replacing runtime syntax highlighting: -

- -

- Static string content props are highlighted at build time and replaced with pre-rendered - HTML. Dynamic content is left unchanged for runtime highlighting. -

-
- - -

- styles - Svelte - by default, originally based on - prism-svelte - by @pngwn: -

- '} /> -

styled:

-
- - import Card from '@fuz.dev/fuz-library/Card.svelte'; - console.log('hello Card', Card); - - - -
hi {friend}
-
`} - /> -
-
- - -

- supports TypeScript with lang="ts": -

- `} /> -
- = ('b' | 3) & T;`} /> -
-
- - -

- supports Markdown with lang="md", and fenced blocks - for all languages: -

- `} /> -
- -
-
- - -

- Passing lang={'{'}null} disables syntax styling: -

- '} /> - all is gray`} /> -
- - -

- is a block by default: -

-
ab
- ab
'} /> -

- It can be inlined with `} /> -

- - - -

- fuz_code can be used directly from TypeScript without Svelte. Import for a pre-configured instance with all built-in languages: -

- -

returns HTML string:

- -

then rendered with:

- {@html programmatic_result}'} /> -

we get:

-

- - {@html programmatic_result} -

-

- For a custom configuration, create your own and register only - the languages you need: -

- hello', 'html');`} - /> -
- - -

- The component generates HTML with CSS classes for text highlighting. - It also includes experimental support for the CSS Custom Highlight API with - , see the - - for more. -

-
- diff --git a/src/routes/Footer.svelte b/src/routes/Footer.svelte deleted file mode 100644 index cba94e76..00000000 --- a/src/routes/Footer.svelte +++ /dev/null @@ -1,7 +0,0 @@ - - -
- -
diff --git a/src/routes/about/+page.svelte b/src/routes/about/+page.svelte deleted file mode 100644 index b14b628c..00000000 --- a/src/routes/about/+page.svelte +++ /dev/null @@ -1,37 +0,0 @@ - - -
-
-
-

{library.repo_name}

-
- -
-
-
- -
-
- -
- -
- -
-
-
-
diff --git a/src/routes/benchmark/benchmark_fixtures.ts b/src/routes/benchmark/benchmark_fixtures.ts index 4f0c7b25..a9b37e9f 100644 --- a/src/routes/benchmark/benchmark_fixtures.ts +++ b/src/routes/benchmark/benchmark_fixtures.ts @@ -44,5 +44,5 @@ export const implementations: Array = [ ]; // Languages to test -export const languages = ['ts', 'css', 'html', 'json', 'svelte', 'md', 'bash'] as const; +export const languages = ['ts', 'css', 'html', 'json', 'svelte', 'md', 'sh'] as const; export type Language = (typeof languages)[number]; diff --git a/src/routes/docs/benchmark/+page.svelte b/src/routes/docs/benchmark/+page.svelte deleted file mode 100644 index 6a605e16..00000000 --- a/src/routes/docs/benchmark/+page.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - -
-

Performance benchmarking for testing syntax highlighting across all supported languages:

-

- run benchmark -

-
-
diff --git a/src/routes/docs/library/+page.svelte b/src/routes/docs/library/+page.svelte deleted file mode 100644 index 18d71fe8..00000000 --- a/src/routes/docs/library/+page.svelte +++ /dev/null @@ -1,17 +0,0 @@ - - - -
- -
-
diff --git a/src/routes/docs/samples/+page.svelte b/src/routes/docs/samples/+page.svelte index 1c2d0418..88fcfe96 100644 --- a/src/routes/docs/samples/+page.svelte +++ b/src/routes/docs/samples/+page.svelte @@ -1,23 +1,130 @@ -
-

- Here's some code samples in all supported languages, rendered with both - (standard HTML mode) and (experimental CSS Custom Highlight - API): -

-

- view samples -

-
+ {#if at_root} +
+

+ Syntax-highlighted in every supported language, rendered with + and the experimental . +

+
+ {:else} +
+

+ Code samples in every supported language. Filter by language, and toggle the renderers: + (standard HTML with .token_* classes) and the + experimental (CSS Custom Highlight API, ranges). With + both on, each sample renders side by side. +

+ +
+ {#each sample_langs as lang (lang)} + + {/each} +
+ +
+ + +
+ + {#if show_highlight} + + {/if} + + {#each shown_samples as sample (sample.name)} +
+

{sample.lang}

+
+ {#if show_html} +
+ +
+ {/if} + {#if show_highlight} +
+ +
+ {/if} +
+ {#if !show_html && !show_highlight} +

Select a renderer above.

+ {/if} +
+ {/each} +
+ {/if}
diff --git a/src/routes/docs/textarea/+page.svelte b/src/routes/docs/textarea/+page.svelte new file mode 100644 index 00000000..034cc5ac --- /dev/null +++ b/src/routes/docs/textarea/+page.svelte @@ -0,0 +1,87 @@ + + + + {#if at_root} +
+

+ An experimental editable, live-highlighted backed by + . +

+
+ {:else} +
+

+ highlights an editable <textarea> + live using the + CSS Custom Highlight API. Type below to see it re-highlight. Requires theme_highlight.css. +

+ {#if !supported} +

+ ⚠️ This browser does not support the CSS Custom Highlight API — the textarea still works, + but text is shown without highlighting. +

+ {/if} + +
+ {#each sample_langs as l (l)} + + {/each} +
+ + +
+ {/if} +
+ + diff --git a/src/routes/docs/tomes.ts b/src/routes/docs/tomes.ts index a138eecf..bcfa07bf 100644 --- a/src/routes/docs/tomes.ts +++ b/src/routes/docs/tomes.ts @@ -1,16 +1,15 @@ import type {Tome} from '@fuzdev/fuz_ui/tome.ts'; import UsagePage from './usage/+page.svelte'; -import ApiPage from './api/+page.svelte'; -import LibraryPage from './library/+page.svelte'; import SamplesPage from './samples/+page.svelte'; -import BenchmarkPage from './benchmark/+page.svelte'; +import TextareaPage from './textarea/+page.svelte'; +import ApiPage from './api/+page.svelte'; export const tomes: Array = [ { slug: 'usage', category: 'guide', Component: UsagePage, - related_tomes: ['api', 'samples'], + related_tomes: ['samples', 'textarea'], related_modules: [ 'syntax_styler.ts', 'syntax_styler_global.ts', @@ -18,35 +17,27 @@ export const tomes: Array = [ ], related_declarations: ['Code', 'SyntaxStyler', 'syntax_styler_global'], }, - { - slug: 'api', - category: 'reference', - Component: ApiPage, - related_tomes: ['usage'], - related_modules: [], - related_declarations: [], - }, - { - slug: 'library', - category: 'reference', - Component: LibraryPage, - related_tomes: [], - related_modules: [], - related_declarations: [], - }, { slug: 'samples', category: 'explore', Component: SamplesPage, - related_tomes: ['usage', 'benchmark'], + related_tomes: ['usage', 'textarea'], related_modules: [], related_declarations: ['Code', 'CodeHighlight'], }, { - slug: 'benchmark', + slug: 'textarea', category: 'explore', - Component: BenchmarkPage, - related_tomes: ['samples'], + Component: TextareaPage, + related_tomes: ['usage', 'samples'], + related_modules: [], + related_declarations: ['CodeTextarea', 'CodeHighlight'], + }, + { + slug: 'api', + category: 'reference', + Component: ApiPage, + related_tomes: ['usage'], related_modules: [], related_declarations: [], }, diff --git a/src/routes/docs/usage/+page.svelte b/src/routes/docs/usage/+page.svelte index f59ac1de..9c131996 100644 --- a/src/routes/docs/usage/+page.svelte +++ b/src/routes/docs/usage/+page.svelte @@ -1,14 +1,201 @@ + + - +
+

+ The + + Svelte component supports syntax styling with hand-written per-language lexers. fuz_code originated + as a fork of + Prism (prismjs.com) by + Lea Verou. +

+

To use it, import the default theme or your own:

+ +

then use :

+ \n\t// Something.svelte\n\timport Code from '@fuzdev/fuz_code/Code.svelte';\n\n\n`} + /> +

outputs:

+ +
+ + +

+ By default fuz_code depends on fuz_css to provide + color-schema-aware color variables. If you're not using it, import + theme_variables.css or bring your own: +

+ +
+ + +

+ The svelte_preprocess_fuz_code preprocessor compiles static + content at build time, replacing runtime syntax highlighting: +

+ +

+ Static string content props are highlighted at build time and replaced with pre-rendered + HTML. Dynamic content is left unchanged for runtime highlighting. +

+
+ + +

+ styles + Svelte + by default, originally based on + prism-svelte + by @pngwn (pngwn.at): +

+ '} /> +

styled:

+
+ + import Card from '@fuz.dev/fuz-library/Card.svelte'; + console.log('hello Card', Card); + + + +
hi {friend}
+
`} + /> +
+
+ + +

+ supports TypeScript with lang="ts": +

+ `} /> +
+ = ('b' | 3) & T;`} /> +
+
+ + +

+ supports Markdown with lang="md", and fenced + blocks for all languages: +

+ `} /> +
+ +
+
+ + +

+ Passing lang={'{'}null} disables syntax styling: +

+ '} /> + all is gray`} /> +
+ + +

+ is a block by default: +

+
ab
+ ab'} /> +

+ It can be inlined with `} /> +

+
+ + +

+ fuz_code can be used directly from TypeScript without Svelte. Import for a pre-configured instance with all built-in languages: +

+ +

returns HTML string:

+ +

then rendered with:

+ {@html programmatic_result}'} /> +

we get:

+

+ + {@html programmatic_result} +

+

+ For a custom configuration, create your own and register + only the languages you need: +

+ hello', 'html');`} + /> +
+ + +

+ The component generates HTML with CSS classes for text highlighting. + It also includes experimental support for the CSS Custom Highlight API with + , see the + + for more. +

+
diff --git a/src/routes/lang_color.ts b/src/routes/lang_color.ts new file mode 100644 index 00000000..7b52c302 --- /dev/null +++ b/src/routes/lang_color.ts @@ -0,0 +1,19 @@ +import type {SampleLang} from '$lib/code_sample.ts'; + +// the color classes are applied dynamically (`{lang_colors[lang]}`), so hint +// them for static extraction +// @fuz-classes color_a color_b color_c color_d color_e color_f color_h + +/** + * A `color_*` utility class per demo language, chosen to evoke each language's + * identity, for tinting the language buttons in the docs. + */ +export const lang_colors: Record = { + json: 'color_e', // gold + css: 'color_d', // purple + ts: 'color_a', // blue + html: 'color_c', // red + svelte: 'color_h', // orange + md: 'color_f', // brown + sh: 'color_b', // green +}; diff --git a/src/routes/samples/+page.svelte b/src/routes/samples/+page.svelte deleted file mode 100644 index f4aa2c15..00000000 --- a/src/routes/samples/+page.svelte +++ /dev/null @@ -1,95 +0,0 @@ - - -
-
- -
- -
-

HTML rendering (default)

- - {#each Object.values(samples) as sample (sample.name)} -
-

{sample.lang}

- -
- {/each} -
- -
- -
-

CSS Custom Highlight API (experimental)

- - {#each Object.values(samples) as sample (sample.name)} -
-

{sample.lang}

-
-
-

{sample.lang} html strings

- -
-
-

{sample.lang} highlighted ranges

- -
-
-
-
- {/each} -
- -
-
diff --git a/src/routes/samples/all.gen.ts b/src/routes/samples/all.gen.ts index 6a640a32..f2e1cc2a 100644 --- a/src/routes/samples/all.gen.ts +++ b/src/routes/samples/all.gen.ts @@ -9,7 +9,7 @@ import {sample_langs} from '$lib/code_sample.ts'; export const gen: Gen = async ({origin_path}) => { // Discover all sample files dynamically const sample_files = await fs_search('src/test/fixtures/samples', { - file_filter: (path) => /sample_[^/]+\.(ts|css|html|json|svelte|md|bash)$/.test(path), + file_filter: (path) => /sample_[^/]+\.(ts|css|html|json|svelte|md|sh)$/.test(path), }); // Create flat structure with lang_variant keys diff --git a/src/routes/samples/all.ts b/src/routes/samples/all.ts index f2994874..2853a57a 100644 --- a/src/routes/samples/all.ts +++ b/src/routes/samples/all.ts @@ -9,7 +9,7 @@ export type SampleKey = | 'html_complex' | 'svelte_complex' | 'md_complex' - | 'bash_complex'; + | 'sh_complex'; export const samples: Record = { json_complex: { @@ -670,9 +670,9 @@ const example = 'embedded code'; `, }, - bash_complex: { - name: 'bash_complex', - lang: 'bash', + sh_complex: { + name: 'sh_complex', + lang: 'sh', content: `#!/bin/bash # This is a comment diff --git a/src/routes/style.css b/src/routes/style.css deleted file mode 100644 index b85e234d..00000000 --- a/src/routes/style.css +++ /dev/null @@ -1,5 +0,0 @@ -/* TODO upstream to fuz_css? */ -.constrain-width { - min-width: 0; - max-width: 100%; -} diff --git a/src/routes/textarea/+page.svelte b/src/routes/textarea/+page.svelte deleted file mode 100644 index 18c1dfa3..00000000 --- a/src/routes/textarea/+page.svelte +++ /dev/null @@ -1,69 +0,0 @@ - - -
-
- -
- -
-

Editable highlighted textarea (experimental)

- - -
- {#each sample_langs as l (l)} - - {/each} -
- - -
- -
-
- - diff --git a/src/test/fixtures/generated/bash/bash_complex.html b/src/test/fixtures/generated/sh/sh_complex.html similarity index 100% rename from src/test/fixtures/generated/bash/bash_complex.html rename to src/test/fixtures/generated/sh/sh_complex.html diff --git a/src/test/fixtures/generated/bash/bash_complex.txt b/src/test/fixtures/generated/sh/sh_complex.txt similarity index 100% rename from src/test/fixtures/generated/bash/bash_complex.txt rename to src/test/fixtures/generated/sh/sh_complex.txt diff --git a/src/test/fixtures/helpers.ts b/src/test/fixtures/helpers.ts index d3fa04b2..65f5904c 100644 --- a/src/test/fixtures/helpers.ts +++ b/src/test/fixtures/helpers.ts @@ -22,7 +22,7 @@ export interface GeneratedOutput { */ export const discover_samples = async (): Promise> => { const sample_files = await fs_search('src/test/fixtures/samples', { - file_filter: (path) => /sample_[^/]+\.(ts|css|html|json|svelte|md|bash)$/.test(path), + file_filter: (path) => /sample_[^/]+\.(ts|css|html|json|svelte|md|sh)$/.test(path), }); const samples: Array = []; diff --git a/src/test/fixtures/samples/sample_complex.bash b/src/test/fixtures/samples/sample_complex.sh similarity index 100% rename from src/test/fixtures/samples/sample_complex.bash rename to src/test/fixtures/samples/sample_complex.sh diff --git a/src/test/lexer_bash.test.ts b/src/test/lexer_bash.test.ts index 45c80edb..764f6397 100644 --- a/src/test/lexer_bash.test.ts +++ b/src/test/lexer_bash.test.ts @@ -293,7 +293,7 @@ describe('lexer_bash comments', () => { }); describe('lexer_bash sample', () => { - const content = readFileSync('src/test/fixtures/samples/sample_complex.bash', 'utf8'); + const content = readFileSync('src/test/fixtures/samples/sample_complex.sh', 'utf8'); test('sample lexes with valid invariants', () => { const lexed = syntax_styler_global.lex(content, 'bash'); diff --git a/src/test/pathological.ts b/src/test/pathological.ts index 840a27ce..583f3991 100644 --- a/src/test/pathological.ts +++ b/src/test/pathological.ts @@ -195,7 +195,7 @@ export const PATHOLOGICAL_CASES: Array = [ { // command substitutions NESTING_DEPTH deep name: 'bash_deep_command_sub', - lang: 'bash', + lang: 'sh', generate: (size) => repeat_to_size( 'echo $('.repeat(NESTING_DEPTH) + 'ls' + ')'.repeat(NESTING_DEPTH) + '\n', @@ -215,7 +215,7 @@ export const PATHOLOGICAL_CASES: Array = [ { // one command-substitution nest as deep as the input allows name: 'bash_cmdsub_full_depth', - lang: 'bash', + lang: 'sh', generate: (size: number): string => { const depth = Math.max(1, Math.floor(size / 3)); return '$('.repeat(depth) + 'x' + ')'.repeat(depth); @@ -227,7 +227,7 @@ export const PATHOLOGICAL_CASES: Array = [ // delimiter must be discovered in the same forward pass (a per-heredoc // close-prescan would be O(depth²), the bug this case guards against) name: 'bash_heredoc_sub_full_depth', - lang: 'bash', + lang: 'sh', generate: (size: number): string => { const depth = Math.max(1, Math.floor(size / 12)); return '$(cat < = [ // many sequential heredocs with expanded, substitution-bearing bodies — // exercises the line-walk close scan and the suspend/resume drain at scale name: 'bash_heredoc_dense', - lang: 'bash', + lang: 'sh', generate: (size) => repeat_to_size('cat < Date: Fri, 10 Jul 2026 22:33:43 -0400 Subject: [PATCH 02/15] wip --- src/routes/docs/samples/+page.svelte | 27 ++++++++++++++++++++++----- src/routes/docs/usage/+page.svelte | 2 +- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/routes/docs/samples/+page.svelte b/src/routes/docs/samples/+page.svelte index 88fcfe96..8053e6c0 100644 --- a/src/routes/docs/samples/+page.svelte +++ b/src/routes/docs/samples/+page.svelte @@ -36,6 +36,9 @@ let show_html = $state(true); let show_highlight = $state(false); + // each renderer takes half the row when both show, otherwise the full width + const column_width = $derived(show_html && show_highlight ? '50%' : '100%'); + const shown_samples = $derived( Object.values(samples).filter((sample) => shown_langs.has(sample.lang)), ); @@ -80,7 +83,7 @@ class:selected={show_html} onclick={() => (show_html = !show_html)} > - html + html renderer @@ -108,14 +111,14 @@ {#each shown_samples as sample (sample.name)}

{sample.lang}

-
+
{#if show_html} -
+
{/if} {#if show_highlight} -
+
{/if} @@ -128,3 +131,17 @@
{/if} + + diff --git a/src/routes/docs/usage/+page.svelte b/src/routes/docs/usage/+page.svelte index 9c131996..f296d169 100644 --- a/src/routes/docs/usage/+page.svelte +++ b/src/routes/docs/usage/+page.svelte @@ -192,7 +192,7 @@ const html = styler.stylize('
hello
', 'html');`}

The component generates HTML with CSS classes for text highlighting. - It also includes experimental support for the CSS Custom Highlight API with + fuz_code also includes experimental support for the CSS Custom Highlight API with , see the for more. From 7dd8325f9943941cec98a266a2126f1cb7200ec7 Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Fri, 10 Jul 2026 23:01:01 -0400 Subject: [PATCH 03/15] improve benchmarks --- src/routes/benchmark/BenchmarkHarness.svelte | 87 +++++++---- src/routes/benchmark/BenchmarkInstance.svelte | 22 ++- src/routes/benchmark/BenchmarkResults.svelte | 7 + src/routes/benchmark/benchmark_results.ts | 4 + src/routes/benchmark/benchmark_runner.ts | 57 ++++--- src/routes/benchmark/benchmark_stats.ts | 11 +- src/routes/benchmark/benchmark_types.ts | 21 ++- src/routes/docs/benchmark/+page.svelte | 143 ++++++++++++++++++ src/routes/docs/tomes.ts | 13 +- 9 files changed, 300 insertions(+), 65 deletions(-) create mode 100644 src/routes/docs/benchmark/+page.svelte diff --git a/src/routes/benchmark/BenchmarkHarness.svelte b/src/routes/benchmark/BenchmarkHarness.svelte index 200be76e..4534baa0 100644 --- a/src/routes/benchmark/BenchmarkHarness.svelte +++ b/src/routes/benchmark/BenchmarkHarness.svelte @@ -1,9 +1,8 @@ diff --git a/src/routes/benchmark/BenchmarkResults.svelte b/src/routes/benchmark/BenchmarkResults.svelte index 25eed517..e2a9b5e3 100644 --- a/src/routes/benchmark/BenchmarkResults.svelte +++ b/src/routes/benchmark/BenchmarkResults.svelte @@ -95,6 +95,13 @@

Legend

    +
  • + Mean / Median / percentiles: work time — stylize + DOM commit + layout, + the highlighter's compute cost +
  • +
  • + Paint: mean time until pixels settle (work plus ~1–2 animation frames) +
  • CV: Coefficient of Variation (std_dev/mean) - lower is better, <15% is good diff --git a/src/routes/benchmark/benchmark_results.ts b/src/routes/benchmark/benchmark_results.ts index 8deae9d6..743ceccc 100644 --- a/src/routes/benchmark/benchmark_results.ts +++ b/src/routes/benchmark/benchmark_results.ts @@ -35,6 +35,10 @@ export const RESULT_COLUMNS: Array = [ header: 'Mean (ms)', get_value: (r) => fmt(ns_to_ms(r.stats.core.mean_ns)), }, + { + header: 'Paint (ms)', + get_value: (r) => fmt(ns_to_ms(r.stats.paint.mean_ns)), + }, { header: 'Median (ms)', get_value: (r) => fmt(ns_to_ms(r.stats.core.p50_ns)), diff --git a/src/routes/benchmark/benchmark_runner.ts b/src/routes/benchmark/benchmark_runner.ts index e4c95017..ce6c0daf 100644 --- a/src/routes/benchmark/benchmark_runner.ts +++ b/src/routes/benchmark/benchmark_runner.ts @@ -27,6 +27,9 @@ const MIN_VALID_TIMING_MS = 0.01; const MAX_VALID_TIMING_MS = 60000; const SUSPICIOUS_MEAN_MS = 0.1; const HIGH_OUTLIER_RATIO = 0.2; +// Cap the rolling jitter window so it can't grow unbounded across a long suite; +// `calculate_timing_jitter` only reads the last handful of entries anyway. +const MAX_RECENT_TIMINGS = 50; // Warmup phase using harness with large content export const warmup_phase = async ( @@ -60,7 +63,8 @@ export const measurement_phase = async ( on_progress?: () => void, should_stop?: () => boolean, ): Promise => { - const times_ms: Array = []; + const work_ms: Array = []; + const paint_ms: Array = []; const stability_checks = []; const timestamps = []; @@ -93,35 +97,46 @@ export const measurement_phase = async ( console.log(`[Measurement] Running iteration ${i + 1}...`); try { - const elapsed = await harness.run_iteration(impl.component, props); - - // Validate the timing - if (elapsed <= 0) { - console.warn(`[Measurement] Suspicious timing (${elapsed}ms) - marking as failed`); - times_ms.push(NaN); + const {work_ms: work, paint_ms: paint} = await harness.run_iteration(impl.component, props); + + // Validate against the work time — the highlighter's compute cost — and + // keep the two series index-aligned (a failed iteration is NaN in both). + if (work <= 0) { + console.warn(`[Measurement] Suspicious timing (${work}ms) - marking as failed`); + work_ms.push(NaN); + paint_ms.push(NaN); timestamps.push(Date.now()); - } else if (elapsed < MIN_VALID_TIMING_MS) { - console.warn( - `[Measurement] Suspiciously fast timing (${elapsed}ms) - possible no-op render`, - ); - times_ms.push(elapsed); + } else if (work < MIN_VALID_TIMING_MS) { + console.warn(`[Measurement] Suspiciously fast timing (${work}ms) - possible no-op render`); + work_ms.push(work); + paint_ms.push(paint); timestamps.push(Date.now()); - recent_timings.push(elapsed); - } else if (elapsed > MAX_VALID_TIMING_MS) { - console.warn(`[Measurement] Extremely slow timing (${elapsed}ms) - possible hang`); - times_ms.push(NaN); + recent_timings.push(work); + } else if (work > MAX_VALID_TIMING_MS) { + console.warn(`[Measurement] Extremely slow timing (${work}ms) - possible hang`); + work_ms.push(NaN); + paint_ms.push(NaN); timestamps.push(Date.now()); } else { - console.log(`[Measurement] Iteration ${i + 1} complete: ${elapsed.toFixed(2)}ms`); - times_ms.push(elapsed); + console.log( + `[Measurement] Iteration ${i + 1} complete: ${work.toFixed(2)}ms work / ${paint.toFixed(2)}ms paint`, + ); + work_ms.push(work); + paint_ms.push(paint); timestamps.push(Date.now()); - recent_timings.push(elapsed); + recent_timings.push(work); + } + + // Bound the rolling jitter window (see `MAX_RECENT_TIMINGS`). + if (recent_timings.length > MAX_RECENT_TIMINGS) { + recent_timings.splice(0, recent_timings.length - MAX_RECENT_TIMINGS); } } catch (error) { console.error(`[Measurement] Iteration ${i + 1} failed:`, error); // Continue with next iteration instead of failing entire test // Record a null/NaN to indicate failure - times_ms.push(NaN); + work_ms.push(NaN); + paint_ms.push(NaN); timestamps.push(Date.now()); } @@ -134,7 +149,7 @@ export const measurement_phase = async ( } } - return {times_ms, stability_checks, timestamps}; + return {work_ms, paint_ms, stability_checks, timestamps}; }; // Run complete benchmark suite diff --git a/src/routes/benchmark/benchmark_stats.ts b/src/routes/benchmark/benchmark_stats.ts index aeeccd79..f84574de 100644 --- a/src/routes/benchmark/benchmark_stats.ts +++ b/src/routes/benchmark/benchmark_stats.ts @@ -18,11 +18,11 @@ import type { * Converts millisecond timings to nanoseconds for fuz_util's BenchmarkStats. */ export const analyze_results = (data: MeasurementData): BrowserBenchmarkStats => { - // Convert milliseconds to nanoseconds for BenchmarkStats - const timings_ns = data.times_ms.map((ms) => ms * TIME_NS_PER_MS); - - // Create core stats using fuz_util - const core = new BenchmarkStats(timings_ns); + // Convert milliseconds to nanoseconds for BenchmarkStats. `core` is the work + // time (the highlighter's compute cost); `paint` adds the frames until pixels + // settle. Both series are index-aligned, so failed iterations drop out of each. + const core = new BenchmarkStats(data.work_ms.map((ms) => ms * TIME_NS_PER_MS)); + const paint = new BenchmarkStats(data.paint_ms.map((ms) => ms * TIME_NS_PER_MS)); // Calculate browser-specific stability metrics const unstable_count = data.stability_checks.filter((s) => !s.is_stable).length; @@ -31,6 +31,7 @@ export const analyze_results = (data: MeasurementData): BrowserBenchmarkStats => return { core, + paint, stability_ratio, unstable_iterations: unstable_count, }; diff --git a/src/routes/benchmark/benchmark_types.ts b/src/routes/benchmark/benchmark_types.ts index 2d690a49..a183496b 100644 --- a/src/routes/benchmark/benchmark_types.ts +++ b/src/routes/benchmark/benchmark_types.ts @@ -30,8 +30,21 @@ export interface StabilityCheck { jitter: number; } +/** + * One iteration's two timings, both measured from the same start (props set): + * `work_ms` ends when the highlight work is committed and laid out; `paint_ms` + * ends once the browser has settled pixels (adds ~1-2 animation frames). + */ +export interface IterationTiming { + work_ms: number; + paint_ms: number; +} + export interface MeasurementData { - times_ms: Array; + /** Per-iteration work time — stylize + DOM commit + forced layout (ms). */ + work_ms: Array; + /** Per-iteration paint-settled time — work plus the frames until pixels update (ms). */ + paint_ms: Array; stability_checks: Array; timestamps: Array; } @@ -41,8 +54,10 @@ export interface MeasurementData { * Uses milliseconds for display (mean_ms, median_ms, etc.) while storing nanoseconds internally. */ export interface BrowserBenchmarkStats { - /** Core stats from fuz_util (in nanoseconds) */ + /** Work-time stats — stylize + DOM commit, the highlighter's compute cost (in nanoseconds). */ core: FuzBenchmarkStats; + /** Paint-settled stats — work plus the frames until pixels update (in nanoseconds). */ + paint: FuzBenchmarkStats; /** Browser-specific: ratio of stable iterations (0-1) */ stability_ratio: number; /** Browser-specific: count of unstable iterations */ @@ -72,7 +87,7 @@ export interface BenchmarkHarnessController { run_iteration: ( component: Component, props: BenchmarkComponentProps, - ) => Promise; + ) => Promise; cleanup: () => Promise; } diff --git a/src/routes/docs/benchmark/+page.svelte b/src/routes/docs/benchmark/+page.svelte new file mode 100644 index 00000000..936fea61 --- /dev/null +++ b/src/routes/docs/benchmark/+page.svelte @@ -0,0 +1,143 @@ + + + +
    +

    + fuz_code runs one hand-written single-pass lexer per language with zero regular expressions, + emitting a flat token event stream that renders to HTML in one forward pass. There is no + grammar interpreter and no backtracking, so highlighting cost stays linear in the length of + the input — mid-keystroke or malformed source included. +

    +

    + It is optimized for runtime highlighting. + Shiki + targets build-time use and runs the + Oniguruma regexp engine that TextMate grammars + require, trading runtime speed for grammar and theme coverage. fuz_code trades that coverage for + a small, fast runtime path — so the two aren't substitutes; pick the one that fits the job. +

    +
    + + +

    + The cross-implementation benchmark measures fuz_code against + Prism and Shiki (both the JavaScript and + Oniguruma engines). For end-to-end stylize — lexing plus HTML generation, the realistic + runtime path — fuz_code lands roughly on par with Prism and vastly faster than Shiki: +

    +
    + + + + + + + + + {#each shiki_speedups as { lang, factor } (lang)} + + + + + {/each} + +
    languagefuz_code vs Shiki
    {lang}{factor}
    +
    +

    + Representative stylize results on larger inputs from one machine; absolute + numbers vary by hardware. See the + committed comparison results for the full matrix across engines and + sizes. +

    +
    + + +

    + The interactive benchmark ⚡ runs live in your browser to + get messy real-life results. It times fuz_code's two renderers - the standard HTML path () and the experimental CSS Custom Highlight API path (, ranges) - across every supported language, reporting mean, median, percentiles, + coefficient of variation, and throughput per run, with system-stability gating between + samples. +

    +

    + Set the iteration count, warmup runs, cooldown, and content multiplier, then run it on your + own hardware. For the steadiest numbers, launch Chromium with garbage collection exposed (chromium --js-flags="--expose-gc") so the harness can settle the heap between samples. +

    +
    + + +

    The command-line benchmarks run from a checkout of the repo:

    +
      +
    • + npm run benchmark — the internal suite, timing every sample at normal and 100× sizes + against a local baseline for regression detection. +
    • +
    • + npm run benchmark:vs — the cross-implementation shootout against Prism and Shiki + that produces the comparison results above. +
    • +
    +

    + Both suites include a pathological group of adversarial inputs (deeply nested and degenerate + source) that pins the lexer's worst-case behavior to linear time; the same generators back the linearity + tests. The engine keeps no resumable state, so it re-lexes the whole document on every change rather + than tokenizing incrementally — whole-document lexing is already sub-frame at these speeds. +

    +
    + + +
      +
    • + Zero regular expressions — char-code scanning, native indexOf, keyword maps. +
    • +
    • + One single-pass lexer per language emitting a flat Int32Array event stream, rendered + to HTML in a single forward pass. +
    • +
    • + Linear-time by construction — every scan loop advances, so there is no catastrophic + backtracking on adversarial input. +
    • +
    • Zero runtime dependencies, so nothing heavier than the lexers ships to the browser.
    • +
    +

    + The same entry points power all of it: and the + SyntaxStyler class expose + lex + (the flat event stream) and stylize (HTML). See the + for the API and the for output in every language. +

    +
    +
    diff --git a/src/routes/docs/tomes.ts b/src/routes/docs/tomes.ts index bcfa07bf..020fc484 100644 --- a/src/routes/docs/tomes.ts +++ b/src/routes/docs/tomes.ts @@ -2,6 +2,7 @@ import type {Tome} from '@fuzdev/fuz_ui/tome.ts'; import UsagePage from './usage/+page.svelte'; import SamplesPage from './samples/+page.svelte'; import TextareaPage from './textarea/+page.svelte'; +import BenchmarkPage from './benchmark/+page.svelte'; import ApiPage from './api/+page.svelte'; export const tomes: Array = [ @@ -9,7 +10,7 @@ export const tomes: Array = [ slug: 'usage', category: 'guide', Component: UsagePage, - related_tomes: ['samples', 'textarea'], + related_tomes: ['samples', 'textarea', 'benchmark'], related_modules: [ 'syntax_styler.ts', 'syntax_styler_global.ts', @@ -21,7 +22,7 @@ export const tomes: Array = [ slug: 'samples', category: 'explore', Component: SamplesPage, - related_tomes: ['usage', 'textarea'], + related_tomes: ['usage', 'textarea', 'benchmark'], related_modules: [], related_declarations: ['Code', 'CodeHighlight'], }, @@ -33,6 +34,14 @@ export const tomes: Array = [ related_modules: [], related_declarations: ['CodeTextarea', 'CodeHighlight'], }, + { + slug: 'benchmark', + category: 'explore', + Component: BenchmarkPage, + related_tomes: ['usage', 'samples'], + related_modules: ['syntax_styler.ts', 'syntax_styler_global.ts'], + related_declarations: ['syntax_styler_global', 'SyntaxStyler', 'Code'], + }, { slug: 'api', category: 'reference', From 25af7df2030860009dc721b2ae2f63c83f00ba64 Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Fri, 10 Jul 2026 23:03:18 -0400 Subject: [PATCH 04/15] fix link --- src/routes/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index fb5e3857..cb57a348 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -40,7 +40,7 @@
    docs{#snippet icon()}{site.glyph}{/snippet} samples{#snippet icon()}🎨{/snippet} - benchmark{#snippet icon()}⚡{/snippet} + benchmark{#snippet icon()}⚡{/snippet}
    From f53d0c216b50f7da1341fc5e31bb1f980c2a998c Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Fri, 10 Jul 2026 23:24:32 -0400 Subject: [PATCH 05/15] improve browser benchmark --- src/routes/benchmark/+page.svelte | 3 ++ src/routes/benchmark/BenchmarkHarness.svelte | 36 +++++++++++---- src/routes/benchmark/BenchmarkResults.svelte | 11 ++++- src/routes/docs/benchmark/+page.svelte | 47 +++++++++++++++++++- src/routes/docs/samples/+page.svelte | 15 +++++++ 5 files changed, 102 insertions(+), 10 deletions(-) diff --git a/src/routes/benchmark/+page.svelte b/src/routes/benchmark/+page.svelte index 9ad8e744..970512b2 100644 --- a/src/routes/benchmark/+page.svelte +++ b/src/routes/benchmark/+page.svelte @@ -1,4 +1,6 @@ {#if current_component && current_props} - {#key iteration_key} - - {/key} + + {/if} + + diff --git a/src/routes/benchmark/BenchmarkResults.svelte b/src/routes/benchmark/BenchmarkResults.svelte index e2a9b5e3..dd3e9f3e 100644 --- a/src/routes/benchmark/BenchmarkResults.svelte +++ b/src/routes/benchmark/BenchmarkResults.svelte @@ -1,4 +1,5 @@ @@ -31,7 +44,7 @@ fuz_code runs one hand-written single-pass lexer per language with zero regular expressions, emitting a flat token event stream that renders to HTML in one forward pass. There is no grammar interpreter and no backtracking, so highlighting cost stays linear in the length of - the input — mid-keystroke or malformed source included. + the input, including partial or malformed source.

    It is optimized for runtime highlighting. @@ -95,6 +108,38 @@ >chromium --js-flags="--expose-gc") so the harness can settle the heap between samples.

    +

    + A sample run, per language, of the work time — stylize plus DOM commit — for each renderer. + The default path builds a .token_* span per + token; the experimental path skips that DOM, so it commits + less: +

    +
    + + + + + + + + + + {#each browser_results as { lang, html, ranges } (lang)} + + + + + + {/each} + +
    languageCode (html)CodeHighlight (ranges)
    {lang}{html}{ranges}
    +
    +

    + One run on a single machine, complex samples at the tool's default size — illustrative, not + a spec. The live tool also reports paint-settle time, percentiles, and throughput. +

    diff --git a/src/routes/docs/samples/+page.svelte b/src/routes/docs/samples/+page.svelte index 8053e6c0..11b946bd 100644 --- a/src/routes/docs/samples/+page.svelte +++ b/src/routes/docs/samples/+page.svelte @@ -30,6 +30,13 @@ else shown_langs.add(lang); }; + // `all` flips the whole row between every language and none + const all_shown = $derived(shown_langs.size === sample_langs.length); + const toggle_all = (): void => { + if (all_shown) shown_langs.clear(); + else for (const lang of sample_langs) shown_langs.add(lang); + }; + // renderer toggles — HTML (`Code`) on by default, the experimental CSS // Custom Highlight API (`CodeHighlight` ranges) off by default; with both // on, each sample renders side by side @@ -74,6 +81,14 @@ {lang} {/each} +
From fe3a16d70cd8bb4a2f337aa4c6ae6feac74c7cb0 Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Fri, 10 Jul 2026 23:45:13 -0400 Subject: [PATCH 06/15] benchmark bars --- src/routes/docs/benchmark/+page.svelte | 140 +++++++++++++++++++------ 1 file changed, 107 insertions(+), 33 deletions(-) diff --git a/src/routes/docs/benchmark/+page.svelte b/src/routes/docs/benchmark/+page.svelte index 802a3b69..35dfbbf5 100644 --- a/src/routes/docs/benchmark/+page.svelte +++ b/src/routes/docs/benchmark/+page.svelte @@ -24,18 +24,20 @@ {lang: 'svelte', factor: '~10× faster'}, ]; - // work time (stylize + DOM commit) per language for each renderer, rounded from - // one interactive-benchmark run on a single machine — illustrative, not a spec; - // run the tool for numbers on your own hardware - const browser_results: Array<{lang: string; html: string; ranges: string}> = [ - {lang: 'ts', html: '56 ms', ranges: '16 ms'}, - {lang: 'css', html: '8 ms', ranges: '3 ms'}, - {lang: 'html', html: '18 ms', ranges: '5 ms'}, - {lang: 'json', html: '8 ms', ranges: '2 ms'}, - {lang: 'svelte', html: '63 ms', ranges: '16 ms'}, - {lang: 'md', html: '43 ms', ranges: '14 ms'}, - {lang: 'sh', html: '35 ms', ranges: '12 ms'}, + // work time in ms (stylize + DOM commit) per language for each renderer, rounded + // from one interactive-benchmark run on a single machine — illustrative, not a + // spec; run the tool for numbers on your own hardware + const browser_results: Array<{lang: string; html: number; ranges: number}> = [ + {lang: 'ts', html: 56, ranges: 16}, + {lang: 'css', html: 8, ranges: 3}, + {lang: 'html', html: 18, ranges: 5}, + {lang: 'json', html: 8, ranges: 2}, + {lang: 'svelte', html: 63, ranges: 16}, + {lang: 'md', html: 43, ranges: 14}, + {lang: 'sh', html: 35, ranges: 12}, ]; + // bars scale to the slowest single result so lengths compare across languages + const browser_max = Math.max(...browser_results.flatMap((r) => [r.html, r.ranges])); @@ -52,7 +54,7 @@ targets build-time use and runs the Oniguruma regexp engine that TextMate grammars require, trading runtime speed for grammar and theme coverage. fuz_code trades that coverage for - a small, fast runtime path — so the two aren't substitutes; pick the one that fits the job. + a small, fast runtime path.

@@ -114,30 +116,37 @@ token; the experimental path skips that DOM, so it commits less:

-
- - - - - - - - - - {#each browser_results as { lang, html, ranges } (lang)} - - - - - - {/each} - -
languageCode (html)CodeHighlight (ranges)
{lang}{html}{ranges}
+
+ Code (html) + CodeHighlight (ranges) +
+
+ {#each browser_results as { lang, html, ranges } (lang)} +
{lang}
+
+
+
+
+
+ {html} ms +
+
+
+
+
+ {ranges} ms +
+
+ {/each}

One run on a single machine, complex samples at the tool's default size — illustrative, not - a spec. The live tool also reports paint-settle time, percentiles, and throughput.Lower is better. The benchmark uses complex samples at the tool's default size, so this is + illustrative, not representative of most inputs. The live tool also reports paint-settle + time, percentiles, and throughput.

@@ -186,3 +195,68 @@

+ + From 78999c7a578048f9fe78e7450221ebc140c56ac0 Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Sat, 11 Jul 2026 00:47:06 -0400 Subject: [PATCH 07/15] run benchmarks --- benchmark/compare/compare.ts | 32 +++- benchmark/compare/results.md | 235 +++++++++++++++---------- src/routes/docs/benchmark/+page.svelte | 26 +-- 3 files changed, 183 insertions(+), 110 deletions(-) diff --git a/benchmark/compare/compare.ts b/benchmark/compare/compare.ts index a3bdd4c5..1882d695 100644 --- a/benchmark/compare/compare.ts +++ b/benchmark/compare/compare.ts @@ -112,7 +112,12 @@ export const run_comparison_benchmark = async ( const content = getSampleContent(lang, large); const size_label = large ? 'large' : 'small'; - // Tokenization benchmarks (fuz_code and Prism only) + // Tokenization benchmarks — raw tokenizer, no HTML rendering, so the + // output-shape difference (CSS classes vs Shiki's inlined theme colors) + // drops out and this is the cleanest apples-to-apples view. Shiki's + // `codeToTokensBase` is its lowest-level public tokenizer (single theme); + // it resolves per-token colors but the grammar walk dominates, so it's a + // fair analog to fuz_code's `lex` and Prism's `tokenize`. bench.add(`fuz_code_tokenize_${lang}_${size_label}`, () => { syntax_styler_global.lex(content, fuz_lang); }); @@ -123,6 +128,14 @@ export const run_comparison_benchmark = async ( }); } + bench.add(`shiki_js_tokenize_${lang}_${size_label}`, () => { + shiki_js.codeToTokensBase(content, {lang: shiki_lang, theme: 'nord'}); + }); + + bench.add(`shiki_oniguruma_tokenize_${lang}_${size_label}`, () => { + shiki_oniguruma.codeToTokensBase(content, {lang: shiki_lang, theme: 'nord'}); + }); + // Stylization benchmarks (all implementations) bench.add(`fuz_code_stylize_${lang}_${size_label}`, () => { syntax_styler_global.stylize(content, fuz_lang); @@ -159,10 +172,11 @@ export const run_comparison_benchmark = async ( * column headers like `vs fuz_code_stylize_ts_large`). * * Each filter narrows by name suffix (`_${lang}_${size}$`) plus an operation - * substring so e.g. the "ts tokenize small" group catches only - * `fuz_code_tokenize_ts_small` and `prism_tokenize_ts_small`, not the stylize - * variants. The trailing `$` matters — without it, "ts_small" would also match - * "ts_small_*" if such a name ever existed. + * substring so e.g. the "ts tokenize small" group catches + * `fuz_code_tokenize_ts_small`, `prism_tokenize_ts_small`, and the two + * `shiki_*_tokenize_ts_small` variants, not the stylize ones. The trailing `$` + * matters — without it, "ts_small" would also match "ts_small_*" if such a name + * ever existed. */ const build_groups = (languages: ReadonlyArray): Array => { const groups: Array = []; @@ -192,6 +206,14 @@ export const format_comparison_results = (results: Array): stri '', 'Comparing fuz_code vs Prism vs Shiki across multiple languages and content sizes.', '', + '`tokenize` rows compare raw tokenizers with no HTML rendering — the', + 'shape-neutral, apples-to-apples view. `stylize` rows compare the HTML each', + 'library actually ships: fuz_code and Prism emit CSS-class spans', + '(``) styled by a static stylesheet, while Shiki has no', + 'native class output and inlines resolved theme colors on every span plus a', + '`
` wrapper — so its `stylize` cost includes per-token color work the',
+		'class-based libraries offload to CSS.',
+		'',
 		'## Results',
 		'',
 		benchmark_format_markdown_grouped(results, groups),
diff --git a/benchmark/compare/results.md b/benchmark/compare/results.md
index fb5d0744..baa23ee6 100644
--- a/benchmark/compare/results.md
+++ b/benchmark/compare/results.md
@@ -2,145 +2,192 @@
 
 Comparing fuz_code vs Prism vs Shiki across multiple languages and content sizes.
 
+`tokenize` rows compare raw tokenizers with no HTML rendering — the
+shape-neutral, apples-to-apples view. `stylize` rows compare the HTML each
+library actually ships: fuz_code and Prism emit CSS-class spans
+(``) styled by a static stylesheet, while Shiki has no
+native class output and inlines resolved theme colors on every span plus a
+`
` wrapper — so its `stylize` cost includes per-token color work the
+class-based libraries offload to CSS.
+
 ## Results
 
 ### ts tokenize (small)
 
-| Task Name                  | ops/sec  | p50 (μs) | p75 (μs) | p90 (μs) | p95 (μs) | p99 (μs) | min (μs) | max (μs) | vs Best  |
-| -------------------------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_tokenize_ts_small | 1,800.91 |   422.27 |   785.68 |   808.71 |   824.34 |   856.34 |   379.51 |   880.66 |    1.60x |
-| prism_tokenize_ts_small    | 2,876.79 |   346.81 |   350.92 |   357.42 |   362.12 |   370.37 |   336.36 |   374.03 | baseline |
+| Task Name                         | ops/sec   | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
+| --------------------------------- | --------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_tokenize_ts_small        | 32,466.07 |     0.03 |     0.03 |     0.03 |     0.04 |     0.05 |     0.03 |     3.86 | baseline |
+| prism_tokenize_ts_small           |  2,921.07 |     0.34 |     0.35 |     0.36 |     0.38 |     0.51 |     0.33 |     2.09 |   11.11x |
+| shiki_js_tokenize_ts_small        |    123.47 |     8.06 |     8.27 |     8.70 |     9.01 |    10.25 |     7.67 |    12.25 |  262.94x |
+| shiki_oniguruma_tokenize_ts_small |    168.09 |     5.92 |     6.18 |     6.67 |     7.32 |     8.57 |     5.55 |    13.10 |  193.14x |
+
 ### ts tokenize (large)
 
-| Task Name                  | ops/sec | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
-| -------------------------- | ------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_tokenize_ts_large |   11.39 |    87.15 |    88.17 |    89.02 |    93.38 |    93.66 |    86.07 |    93.78 |    1.76x |
-| prism_tokenize_ts_large    |   20.00 |    49.87 |    50.82 |    51.72 |    52.29 |    53.56 |    48.14 |    54.53 | baseline |
+| Task Name                         | ops/sec | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
+| --------------------------------- | ------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_tokenize_ts_large        |  290.93 |     3.44 |     3.49 |     3.66 |     3.79 |     4.83 |     3.26 |     7.03 | baseline |
+| prism_tokenize_ts_large           |   19.70 |    50.75 |    52.40 |    58.21 |    59.73 |    60.64 |    48.51 |    62.28 |   14.77x |
+| shiki_js_tokenize_ts_large        |    0.94 |  1059.10 |  1063.36 |  1065.29 |  1065.78 |  1066.18 |  1051.07 |  1066.27 |  307.95x |
+| shiki_oniguruma_tokenize_ts_large |    1.69 |   592.71 |   594.61 |   601.29 |   603.11 |   604.31 |   582.09 |   604.61 |  172.62x |
+
 ### ts stylize (small)
 
-| Task Name                        | ops/sec  | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
-| -------------------------------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_stylize_ts_small        | 1,760.58 |     0.57 |     0.57 |     0.58 |     0.59 |     0.61 |     0.55 |     0.61 | baseline |
-| prism_stylize_ts_small           | 1,757.10 |     0.57 |     0.58 |     0.59 |     0.60 |     0.61 |     0.55 |     0.62 |    1.00x |
-| shiki_js_stylize_ts_small        |   106.61 |     9.35 |     9.50 |     9.65 |     9.74 |     9.90 |     9.02 |    10.15 |   16.51x |
-| shiki_oniguruma_stylize_ts_small |   139.30 |     7.10 |     7.33 |     7.53 |     7.74 |     8.04 |     6.83 |     8.14 |   12.64x |
+| Task Name                        | ops/sec   | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
+| -------------------------------- | --------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_stylize_ts_small        | 19,441.89 |     0.05 |     0.05 |     0.05 |     0.06 |     0.09 |     0.05 |     0.54 | baseline |
+| prism_stylize_ts_small           |  1,762.95 |     0.57 |     0.58 |     0.60 |     0.63 |     0.91 |     0.54 |     1.44 |   11.03x |
+| shiki_js_stylize_ts_small        |     75.83 |    13.20 |    13.57 |    14.71 |    15.43 |    16.05 |    12.09 |    17.68 |  256.40x |
+| shiki_oniguruma_stylize_ts_small |    122.00 |     8.17 |     8.59 |     9.26 |    10.57 |    11.86 |     7.37 |    14.75 |  159.36x |
+
 ### ts stylize (large)
 
 | Task Name                        | ops/sec | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
 | -------------------------------- | ------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_stylize_ts_large        |    7.39 |   122.29 |   153.39 |   155.09 |   156.25 |   159.12 |   112.96 |   159.32 |    1.39x |
-| prism_stylize_ts_large           |   10.30 |   101.82 |   107.15 |   113.31 |   120.89 |   122.71 |    81.86 |   123.42 | baseline |
-| shiki_js_stylize_ts_large        |    0.77 |  1295.46 |  1310.87 |  1311.54 |  1311.59 |  1311.64 |  1278.61 |  1311.65 |   13.37x |
-| shiki_oniguruma_stylize_ts_large |    1.29 |   775.47 |   786.70 |   789.07 |   792.63 |   796.28 |   751.15 |   797.20 |    7.96x |
+| fuz_code_stylize_ts_large        |  174.63 |     5.70 |     5.85 |     6.08 |     6.60 |     9.88 |     5.45 |    10.83 | baseline |
+| prism_stylize_ts_large           |    9.42 |   112.30 |   119.85 |   124.74 |   126.66 |   136.80 |    83.51 |   141.61 |   18.54x |
+| shiki_js_stylize_ts_large        |    0.81 |  1225.51 |  1238.92 |  1242.92 |  1244.87 |  1246.44 |  1214.31 |  1246.83 |  214.61x |
+| shiki_oniguruma_stylize_ts_large |    1.25 |   798.18 |   818.45 |   855.70 |   879.02 |   897.62 |   741.31 |   902.27 |  139.34x |
+
 ### css tokenize (small)
 
-| Task Name                   | ops/sec   | p50 (μs) | p75 (μs) | p90 (μs) | p95 (μs) | p99 (μs) | min (μs) | max (μs) | vs Best  |
-| --------------------------- | --------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_tokenize_css_small | 57,067.36 |    17.49 |    17.76 |    18.25 |    18.63 |    19.13 |    16.39 |    19.45 | baseline |
-| prism_tokenize_css_small    | 54,564.70 |    18.27 |    18.59 |    19.09 |    19.47 |    20.01 |    17.18 |    20.34 |    1.05x |
+| Task Name                          | ops/sec    | p50 (μs) | p75 (μs) | p90 (μs) | p95 (μs) | p99 (μs) | min (μs) | max (μs) | vs Best  |
+| ---------------------------------- | ---------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_tokenize_css_small        | 306,302.85 |     3.28 |     3.56 |     5.13 |     6.14 |     9.53 |     2.98 |  4396.76 | baseline |
+| prism_tokenize_css_small           |  51,543.28 |    19.36 |    20.09 |    23.82 |    30.90 |    41.81 |    18.32 |   562.69 |    5.94x |
+| shiki_js_tokenize_css_small        |   3,856.66 |   257.84 |   272.54 |   305.12 |   369.73 |   551.93 |   227.98 |  5243.98 |   79.42x |
+| shiki_oniguruma_tokenize_css_small |   1,297.18 |   768.32 |   810.38 |   926.11 |  1063.95 |  1469.76 |   696.57 |  4792.35 |  236.13x |
+
 ### css tokenize (large)
 
-| Task Name                   | ops/sec | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
-| --------------------------- | ------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_tokenize_css_large |  577.24 |     1.71 |     1.78 |     1.88 |     1.94 |     2.00 |     1.60 |     2.02 | baseline |
-| prism_tokenize_css_large    |  570.23 |     1.73 |     1.79 |     1.87 |     1.92 |     1.98 |     1.63 |     2.00 |    1.01x |
+| Task Name                          | ops/sec  | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
+| ---------------------------------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_tokenize_css_large        | 3,248.46 |     0.31 |     0.31 |     0.32 |     0.34 |     0.54 |     0.29 |     3.28 | baseline |
+| prism_tokenize_css_large           |   525.80 |     1.89 |     1.99 |     2.22 |     2.82 |     3.50 |     1.72 |     5.61 |    6.18x |
+| shiki_js_tokenize_css_large        |    37.98 |    26.30 |    27.22 |    28.38 |    29.54 |    32.18 |    23.65 |    36.55 |   85.53x |
+| shiki_oniguruma_tokenize_css_large |    12.80 |    77.77 |    79.26 |    81.36 |    81.83 |    85.84 |    75.05 |    89.18 |  253.77x |
+
 ### css stylize (small)
 
-| Task Name                         | ops/sec   | p50 (μs) | p75 (μs) | p90 (μs) | p95 (μs) | p99 (μs) | min (μs) | max (μs) | vs Best  |
-| --------------------------------- | --------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_stylize_css_small        | 25,753.39 |    38.77 |    39.40 |    40.13 |    40.55 |    42.00 |    36.22 |    42.78 | baseline |
-| prism_stylize_css_small           | 21,603.52 |    46.18 |    47.04 |    47.76 |    48.43 |    50.15 |    43.27 |    50.80 |    1.19x |
-| shiki_js_stylize_css_small        |  1,967.37 |   502.83 |   520.16 |   545.22 |   561.10 |   584.30 |   462.31 |   593.79 |   13.09x |
-| shiki_oniguruma_stylize_css_small |    904.88 |  1108.22 |  1153.73 |  1204.62 |  1255.66 |  1353.74 |   963.55 |  1426.17 |   28.46x |
+| Task Name                         | ops/sec    | p50 (μs) | p75 (μs) | p90 (μs) | p95 (μs) | p99 (μs) | min (μs) | max (μs) | vs Best  |
+| --------------------------------- | ---------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_stylize_css_small        | 163,634.94 |     6.09 |     6.36 |     7.15 |     9.07 |    14.05 |     5.45 |   400.93 | baseline |
+| prism_stylize_css_small           |  21,083.33 |    47.51 |    48.40 |    50.92 |    61.85 |    91.72 |    43.35 |  3252.97 |    7.76x |
+| shiki_js_stylize_css_small        |   1,891.12 |   526.62 |   562.66 |   661.94 |   753.08 |  3497.01 |   465.60 |  7216.99 |   86.53x |
+| shiki_oniguruma_stylize_css_small |     996.59 |  1003.30 |  1038.25 |  1135.82 |  1401.73 |  4107.00 |   925.07 |  7005.72 |  164.19x |
+
 ### css stylize (large)
 
-| Task Name                         | ops/sec | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
-| --------------------------------- | ------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_stylize_css_large        |  254.05 |     3.89 |     3.98 |     4.18 |     4.26 |     4.32 |     3.73 |     4.35 | baseline |
-| prism_stylize_css_large           |  215.62 |     4.60 |     4.68 |     4.85 |     4.93 |     5.01 |     4.44 |     5.03 |    1.18x |
-| shiki_js_stylize_css_large        |   19.07 |    52.26 |    52.86 |    53.74 |    55.09 |    55.59 |    50.63 |    55.93 |   13.32x |
-| shiki_oniguruma_stylize_css_large |    8.70 |   114.91 |   115.63 |   116.48 |   116.78 |   117.41 |   111.79 |   118.20 |   29.21x |
+| Task Name                         | ops/sec  | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
+| --------------------------------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_stylize_css_large        | 1,563.61 |     0.64 |     0.65 |     0.66 |     0.69 |     1.25 |     0.60 |     1.86 | baseline |
+| prism_stylize_css_large           |   190.23 |     5.20 |     5.57 |     6.03 |     7.83 |     9.64 |     4.60 |    11.92 |    8.22x |
+| shiki_js_stylize_css_large        |    17.08 |    58.54 |    60.59 |    63.74 |    67.18 |    69.51 |    51.75 |    73.02 |   91.54x |
+| shiki_oniguruma_stylize_css_large |     9.20 |   108.30 |   110.64 |   114.65 |   116.91 |   119.58 |   101.16 |   120.22 |  169.92x |
+
 ### html tokenize (small)
 
-| Task Name                    | ops/sec   | p50 (μs) | p75 (μs) | p90 (μs) | p95 (μs) | p99 (μs) | min (μs) | max (μs) | vs Best  |
-| ---------------------------- | --------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_tokenize_html_small | 14,695.38 |    67.86 |    68.81 |    69.79 |    70.27 |    72.15 |    64.72 |    72.93 |    1.25x |
-| prism_tokenize_html_small    | 18,301.56 |    54.64 |    55.38 |    56.00 |    56.64 |    58.60 |    51.18 |    59.26 | baseline |
+| Task Name                           | ops/sec    | p50 (μs) | p75 (μs) | p90 (μs) | p95 (μs) | p99 (μs) | min (μs) | max (μs) | vs Best  |
+| ----------------------------------- | ---------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_tokenize_html_small        | 109,228.45 |     9.14 |     9.65 |    12.18 |    14.54 |    22.39 |     8.03 |  3783.31 | baseline |
+| prism_tokenize_html_small           |  17,330.90 |    57.93 |    58.83 |    59.58 |    61.80 |    88.10 |    53.66 |   567.89 |    6.30x |
+| shiki_js_tokenize_html_small        |   1,193.09 |   837.34 |   870.58 |   916.51 |  1026.24 |  1848.22 |   765.53 |  4752.86 |   91.55x |
+| shiki_oniguruma_tokenize_html_small |   1,150.60 |   864.19 |   897.42 |   947.73 |  1018.70 |  1840.75 |   814.92 |  3136.35 |   94.93x |
+
 ### html tokenize (large)
 
-| Task Name                    | ops/sec | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
-| ---------------------------- | ------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_tokenize_html_large |  119.02 |     8.31 |     8.58 |     9.02 |     9.28 |     9.69 |     7.84 |     9.82 |    1.44x |
-| prism_tokenize_html_large    |  170.96 |     5.71 |     5.97 |     6.40 |     6.56 |     6.62 |     5.51 |     6.64 | baseline |
+| Task Name                           | ops/sec  | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
+| ----------------------------------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_tokenize_html_large        | 1,219.01 |     0.82 |     0.84 |     0.86 |     0.92 |     1.89 |     0.77 |     4.69 | baseline |
+| prism_tokenize_html_large           |   167.84 |     5.90 |     6.38 |     7.69 |     9.03 |    10.38 |     5.63 |    12.25 |    7.26x |
+| shiki_js_tokenize_html_large        |    12.11 |    82.24 |    83.65 |    86.93 |    87.79 |    89.53 |    78.54 |    92.32 |  100.67x |
+| shiki_oniguruma_tokenize_html_large |    10.65 |    89.80 |   122.17 |   136.51 |   151.98 |   158.48 |    83.59 |   183.76 |  114.42x |
+
 ### html stylize (small)
 
-| Task Name                          | ops/sec  | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
-| ---------------------------------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_stylize_html_small        | 7,505.98 |     0.13 |     0.14 |     0.14 |     0.14 |     0.15 |     0.12 |     0.15 |    1.07x |
-| prism_stylize_html_small           | 8,011.02 |     0.12 |     0.13 |     0.13 |     0.13 |     0.13 |     0.12 |     0.13 | baseline |
-| shiki_js_stylize_html_small        |   868.67 |     1.14 |     1.17 |     1.22 |     1.24 |     1.28 |     1.09 |     1.29 |    9.22x |
-| shiki_oniguruma_stylize_html_small |   819.75 |     1.21 |     1.25 |     1.31 |     1.35 |     1.40 |     1.13 |     1.42 |    9.77x |
+| Task Name                          | ops/sec   | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
+| ---------------------------------- | --------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_stylize_html_small        | 63,517.77 |     0.02 |     0.02 |     0.02 |     0.02 |     0.03 |     0.01 |     0.87 | baseline |
+| prism_stylize_html_small           |  8,030.70 |     0.13 |     0.13 |     0.13 |     0.14 |     0.21 |     0.12 |     2.90 |    7.91x |
+| shiki_js_stylize_html_small        |    852.32 |     1.17 |     1.21 |     1.29 |     1.42 |     2.99 |     1.11 |     5.26 |   74.52x |
+| shiki_oniguruma_stylize_html_small |    826.15 |     1.20 |     1.25 |     1.35 |     1.46 |     3.53 |     1.14 |     5.83 |   76.88x |
+
 ### html stylize (large)
 
 | Task Name                          | ops/sec | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
 | ---------------------------------- | ------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_stylize_html_large        |   52.77 |    18.69 |    19.63 |    20.93 |    21.78 |    22.66 |    17.15 |    22.92 |    1.08x |
-| prism_stylize_html_large           |   57.13 |    17.36 |    18.48 |    19.50 |    20.07 |    21.65 |    15.05 |    22.61 | baseline |
-| shiki_js_stylize_html_large        |    8.07 |   123.75 |   125.68 |   128.04 |   129.15 |   132.25 |   117.52 |   132.52 |    7.08x |
-| shiki_oniguruma_stylize_html_large |    7.56 |   131.65 |   134.84 |   137.91 |   139.19 |   144.76 |   124.59 |   146.06 |    7.56x |
+| fuz_code_stylize_html_large        |  640.42 |     1.57 |     1.60 |     1.66 |     2.19 |     2.54 |     1.47 |     4.91 | baseline |
+| prism_stylize_html_large           |   57.59 |    17.30 |    18.69 |    21.48 |    27.99 |    32.02 |    15.17 |    38.23 |   11.12x |
+| shiki_js_stylize_html_large        |    7.97 |   125.11 |   128.80 |   132.79 |   133.86 |   138.73 |   116.45 |   141.55 |   80.31x |
+| shiki_oniguruma_stylize_html_large |    7.96 |   125.78 |   128.28 |   134.29 |   140.28 |   150.75 |   120.94 |   158.57 |   80.43x |
+
 ### json tokenize (small)
 
-| Task Name                    | ops/sec   | p50 (μs) | p75 (μs) | p90 (μs) | p95 (μs) | p99 (μs) | min (μs) | max (μs) | vs Best  |
-| ---------------------------- | --------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_tokenize_json_small | 62,236.30 |    15.97 |    16.27 |    16.73 |    16.98 |    17.28 |    14.62 |    17.40 |    1.06x |
-| prism_tokenize_json_small    | 65,992.13 |    15.05 |    15.34 |    15.83 |    16.11 |    16.41 |    13.72 |    16.51 | baseline |
+| Task Name                           | ops/sec    | p50 (μs) | p75 (μs) | p90 (μs) | p95 (μs) | p99 (μs) | min (μs) | max (μs) | vs Best  |
+| ----------------------------------- | ---------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_tokenize_json_small        | 487,937.86 |     2.00 |     2.48 |     2.85 |     3.65 |     5.17 |     1.74 |  3578.19 | baseline |
+| prism_tokenize_json_small           |  66,838.82 |    14.93 |    15.30 |    15.93 |    16.85 |    22.53 |    13.90 |   406.38 |    7.30x |
+| shiki_js_tokenize_json_small        |   5,961.86 |   167.31 |   171.58 |   178.21 |   186.98 |   288.62 |   158.69 |  4755.00 |   81.84x |
+| shiki_oniguruma_tokenize_json_small |   5,244.91 |   191.19 |   195.14 |   209.88 |   234.05 |   365.63 |   176.74 |  5591.47 |   93.03x |
+
 ### json tokenize (large)
 
-| Task Name                    | ops/sec | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
-| ---------------------------- | ------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_tokenize_json_large |  584.89 |     1.69 |     1.74 |     1.83 |     1.89 |     1.94 |     1.54 |     1.96 |    1.12x |
-| prism_tokenize_json_large    |  653.66 |     1.51 |     1.55 |     1.64 |     1.68 |     1.73 |     1.41 |     1.74 | baseline |
+| Task Name                           | ops/sec  | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
+| ----------------------------------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_tokenize_json_large        | 5,585.66 |     0.18 |     0.18 |     0.18 |     0.20 |     0.35 |     0.17 |     6.94 | baseline |
+| prism_tokenize_json_large           |   651.45 |     1.53 |     1.60 |     1.85 |     2.20 |     2.75 |     1.44 |     6.21 |    8.57x |
+| shiki_js_tokenize_json_large        |    37.79 |    25.79 |    30.48 |    36.83 |    42.19 |    60.50 |    17.70 |    91.11 |  147.80x |
+| shiki_oniguruma_tokenize_json_large |    44.26 |    21.80 |    25.85 |    30.99 |    34.94 |    40.08 |    19.01 |    46.37 |  126.21x |
+
 ### json stylize (small)
 
-| Task Name                          | ops/sec   | p50 (μs) | p75 (μs) | p90 (μs) | p95 (μs) | p99 (μs) | min (μs) | max (μs) | vs Best  |
-| ---------------------------------- | --------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_stylize_json_small        | 25,427.38 |    39.19 |    39.83 |    40.34 |    40.69 |    41.67 |    36.32 |    42.23 | baseline |
-| prism_stylize_json_small           | 22,397.68 |    44.63 |    45.37 |    46.11 |    46.95 |    48.47 |    40.66 |    48.97 |    1.14x |
-| shiki_js_stylize_json_small        |  2,415.36 |   404.54 |   427.11 |   459.26 |   478.50 |   499.56 |   372.65 |   504.77 |   10.53x |
-| shiki_oniguruma_stylize_json_small |  2,336.40 |   423.00 |   437.55 |   460.71 |   475.04 |   492.08 |   386.43 |   497.01 |   10.88x |
+| Task Name                          | ops/sec    | p50 (μs) | p75 (μs) | p90 (μs) | p95 (μs) | p99 (μs) | min (μs) | max (μs) | vs Best  |
+| ---------------------------------- | ---------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_stylize_json_small        | 230,589.21 |     4.34 |     4.51 |     5.46 |     7.06 |     8.60 |     3.89 |   903.46 | baseline |
+| prism_stylize_json_small           |  23,604.76 |    42.35 |    43.42 |    44.63 |    46.62 |    70.38 |    39.71 |   572.63 |    9.77x |
+| shiki_js_stylize_json_small        |   2,546.67 |   391.11 |   405.00 |   429.26 |   477.65 |  2923.93 |   368.25 |  6914.97 |   90.55x |
+| shiki_oniguruma_stylize_json_small |   2,439.59 |   407.91 |   423.31 |   451.42 |   497.07 |   962.54 |   384.99 |  5192.08 |   94.52x |
+
 ### json stylize (large)
 
-| Task Name                          | ops/sec | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
-| ---------------------------------- | ------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_stylize_json_large        |  241.16 |     4.09 |     4.25 |     4.48 |     4.64 |     4.86 |     3.73 |     4.93 | baseline |
-| prism_stylize_json_large           |  197.89 |     4.91 |     5.31 |     5.82 |     6.11 |     6.41 |     4.38 |     6.57 |    1.22x |
-| shiki_js_stylize_json_large        |   21.05 |    47.21 |    48.41 |    49.80 |    50.67 |    51.91 |    44.06 |    52.64 |   11.46x |
-| shiki_oniguruma_stylize_json_large |   20.32 |    49.32 |    50.23 |    51.34 |    52.11 |    54.72 |    43.71 |    54.87 |   11.87x |
+| Task Name                          | ops/sec  | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
+| ---------------------------------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_stylize_json_large        | 2,047.03 |     0.49 |     0.55 |     0.93 |     1.03 |     1.09 |     0.44 |     4.18 | baseline |
+| prism_stylize_json_large           |   119.31 |     8.00 |     9.84 |    12.12 |    13.23 |    15.77 |     4.57 |    20.07 |   17.16x |
+| shiki_js_stylize_json_large        |    21.35 |    46.31 |    48.91 |    51.50 |    53.22 |    57.96 |    41.99 |    59.51 |   95.89x |
+| shiki_oniguruma_stylize_json_large |    20.27 |    49.54 |    50.67 |    52.00 |    53.47 |    58.27 |    44.68 |    58.73 |  100.97x |
+
 ### svelte tokenize (small)
 
-| Task Name                      | ops/sec  | p50 (μs) | p75 (μs) | p90 (μs) | p95 (μs) | p99 (μs) | min (μs) | max (μs) | vs Best  |
-| ------------------------------ | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_tokenize_svelte_small | 3,016.75 |   329.90 |   336.78 |   347.46 |   353.03 |   360.04 |   299.81 |   362.25 | baseline |
-| prism_tokenize_svelte_small    | 2,829.52 |   350.85 |   359.48 |   370.41 |   377.07 |   386.25 |   320.35 |   389.56 |    1.07x |
+| Task Name                             | ops/sec   | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
+| ------------------------------------- | --------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_tokenize_svelte_small        | 27,383.82 |     0.04 |     0.04 |     0.04 |     0.04 |     0.05 |     0.03 |     0.42 | baseline |
+| prism_tokenize_svelte_small           |  2,748.87 |     0.37 |     0.37 |     0.39 |     0.41 |     0.63 |     0.34 |     1.20 |    9.96x |
+| shiki_js_tokenize_svelte_small        |    113.60 |     8.73 |     9.07 |     9.39 |     9.64 |    10.13 |     8.20 |    14.78 |  241.04x |
+| shiki_oniguruma_tokenize_svelte_small |     98.19 |    10.19 |    10.59 |    11.10 |    11.44 |    12.19 |     8.41 |    14.27 |  278.88x |
+
 ### svelte tokenize (large)
 
-| Task Name                      | ops/sec | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
-| ------------------------------ | ------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_tokenize_svelte_large |   24.98 |    38.32 |    41.83 |    47.66 |    48.54 |    48.84 |    34.78 |    48.95 |    1.09x |
-| prism_tokenize_svelte_large    |   27.28 |    36.31 |    39.72 |    42.41 |    43.90 |    45.67 |    30.60 |    50.20 | baseline |
+| Task Name                             | ops/sec | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
+| ------------------------------------- | ------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_tokenize_svelte_large        |  246.86 |     4.04 |     4.15 |     4.36 |     4.61 |     6.08 |     3.82 |     9.71 | baseline |
+| prism_tokenize_svelte_large           |   27.17 |    36.00 |    39.75 |    42.47 |    43.38 |    46.62 |    30.93 |    52.31 |    9.09x |
+| shiki_js_tokenize_svelte_large        |    1.14 |   873.91 |   912.67 |   934.80 |   970.08 |  1003.06 |   854.82 |  1011.31 |  216.60x |
+| shiki_oniguruma_tokenize_svelte_large |    1.08 |   920.54 |   934.92 |   958.07 |   967.11 |   974.34 |   885.00 |   976.15 |  228.22x |
+
 ### svelte stylize (small)
 
-| Task Name                            | ops/sec  | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
-| ------------------------------------ | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
-| fuz_code_stylize_svelte_small        | 1,860.47 |     0.53 |     0.55 |     0.57 |     0.58 |     0.60 |     0.49 |     0.61 | baseline |
-| prism_stylize_svelte_small           | 1,691.30 |     0.59 |     0.60 |     0.63 |     0.64 |     0.66 |     0.54 |     0.67 |    1.10x |
-| shiki_js_stylize_svelte_small        |    95.79 |    10.31 |    10.87 |    11.41 |    11.71 |    12.22 |     9.28 |    12.54 |   19.42x |
-| shiki_oniguruma_stylize_svelte_small |    88.07 |    11.27 |    11.74 |    12.29 |    12.58 |    12.94 |     9.87 |    13.46 |   21.12x |
+| Task Name                            | ops/sec   | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best  |
+| ------------------------------------ | --------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| fuz_code_stylize_svelte_small        | 17,591.42 |     0.06 |     0.06 |     0.06 |     0.06 |     0.09 |     0.05 |     0.49 | baseline |
+| prism_stylize_svelte_small           |  1,712.97 |     0.58 |     0.60 |     0.63 |     0.67 |     0.95 |     0.55 |     1.55 |   10.27x |
+| shiki_js_stylize_svelte_small        |     99.67 |     9.89 |    10.43 |    10.88 |    11.09 |    11.64 |     9.35 |    17.97 |  176.50x |
+| shiki_oniguruma_stylize_svelte_small |     90.95 |    10.89 |    11.50 |    12.12 |    12.49 |    13.56 |     9.68 |    17.16 |  193.42x |
+
 ### svelte stylize (large)
 
 | Task Name                            | ops/sec | p50 (s) | p75 (s) | p90 (s) | p95 (s) | p99 (s) | min (s) | max (s) | vs Best  |
 | ------------------------------------ | ------- | ------- | ------- | ------- | ------- | ------- | ------- | ------- | -------- |
-| fuz_code_stylize_svelte_large        |    9.81 |    0.10 |    0.12 |    0.13 |    0.13 |    0.14 |    0.06 |    0.14 |    1.06x |
-| prism_stylize_svelte_large           |   10.39 |    0.10 |    0.11 |    0.11 |    0.12 |    0.12 |    0.07 |    0.14 | baseline |
-| shiki_js_stylize_svelte_large        |    0.94 |    1.05 |    1.07 |    1.08 |    1.08 |    1.08 |    1.04 |    1.08 |   11.00x |
-| shiki_oniguruma_stylize_svelte_large |    0.87 |    1.16 |    1.17 |    1.17 |    1.17 |    1.17 |    1.13 |    1.17 |   12.01x |
+| fuz_code_stylize_svelte_large        |  156.96 |    0.01 |    0.01 |    0.01 |    0.01 |    0.01 |    0.01 |    0.01 | baseline |
+| prism_stylize_svelte_large           |   11.03 |    0.10 |    0.10 |    0.11 |    0.11 |    0.12 |    0.06 |    0.12 |   14.23x |
+| shiki_js_stylize_svelte_large        |    0.40 |    2.49 |    2.65 |    2.81 |    2.86 |    2.90 |    2.19 |    2.91 |  395.70x |
+| shiki_oniguruma_stylize_svelte_large |    0.95 |    1.06 |    1.06 |    1.07 |    1.07 |    1.07 |    1.04 |    1.07 |  165.19x |
diff --git a/src/routes/docs/benchmark/+page.svelte b/src/routes/docs/benchmark/+page.svelte
index 35dfbbf5..9d2ade69 100644
--- a/src/routes/docs/benchmark/+page.svelte
+++ b/src/routes/docs/benchmark/+page.svelte
@@ -13,15 +13,16 @@
 
 	const RESULTS_URL = 'https://github.com/fuzdev/fuz_code/blob/main/benchmark/compare/results.md';
 
-	// representative end-to-end `stylize` speedups vs Shiki on larger inputs,
-	// rounded from the committed comparison results — see `RESULTS_URL` for the
+	// representative end-to-end `stylize` speedups on larger (100×) inputs, rounded
+	// from the committed comparison results and measured against the faster of
+	// Shiki's two engines (the conservative choice) — see `RESULTS_URL` for the
 	// full per-engine, per-size matrix
-	const shiki_speedups: Array<{lang: string; factor: string}> = [
-		{lang: 'ts', factor: '~10× faster'},
-		{lang: 'css', factor: '~13× faster'},
-		{lang: 'html', factor: '~7× faster'},
-		{lang: 'json', factor: '~11× faster'},
-		{lang: 'svelte', factor: '~10× faster'},
+	const speedups: Array<{lang: string; vs_prism: string; vs_shiki: string}> = [
+		{lang: 'ts', vs_prism: '~18× faster', vs_shiki: '~140× faster'},
+		{lang: 'css', vs_prism: '~8× faster', vs_shiki: '~90× faster'},
+		{lang: 'html', vs_prism: '~11× faster', vs_shiki: '~80× faster'},
+		{lang: 'json', vs_prism: '~17× faster', vs_shiki: '~95× faster'},
+		{lang: 'svelte', vs_prism: '~14× faster', vs_shiki: '~165× faster'},
 	];
 
 	// work time in ms (stylize + DOM commit) per language for each renderer, rounded
@@ -63,21 +64,24 @@
 			The cross-implementation benchmark measures fuz_code against
 			Prism and Shiki (both the JavaScript and
 			Oniguruma engines). For end-to-end stylize — lexing plus HTML generation, the realistic
-			runtime path — fuz_code lands roughly on par with Prism and vastly faster than Shiki:
+			runtime path — fuz_code runs roughly an order of magnitude faster than Prism and about two orders
+			of magnitude faster than Shiki:
 		

+ - {#each shiki_speedups as { lang, factor } (lang)} + {#each speedups as { lang, vs_prism, vs_shiki } (lang)} - + + {/each} From 03e211b210badcba20ebb2c6b2d734d313e168e5 Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Sat, 11 Jul 2026 07:53:16 -0400 Subject: [PATCH 08/15] run benchmarks --- benchmark/results.md | 125 ++++++++++++++++++++++++++++++------------- 1 file changed, 89 insertions(+), 36 deletions(-) diff --git a/benchmark/results.md b/benchmark/results.md index 2835d40a..e5b1caf8 100644 --- a/benchmark/results.md +++ b/benchmark/results.md @@ -8,50 +8,103 @@ | Task Name | ops/sec | p50 (μs) | p75 (μs) | p90 (μs) | p95 (μs) | p99 (μs) | min (μs) | max (μs) | vs Best | | ----------------------- | ---------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -| baseline:json_complex | 243,220.27 | 4.12 | 4.46 | 7.23 | 8.25 | 11.85 | 3.71 | 1899.81 | baseline | -| baseline:css_complex | 161,243.83 | 6.21 | 6.48 | 9.99 | 11.54 | 17.02 | 5.77 | 5623.44 | 1.51x | -| baseline:ts_complex | 17,591.93 | 56.87 | 57.82 | 62.92 | 76.14 | 108.89 | 53.03 | 5454.17 | 13.83x | -| baseline:html_complex | 64,995.01 | 15.35 | 15.97 | 19.97 | 25.49 | 32.18 | 14.03 | 3856.62 | 3.74x | -| baseline:svelte_complex | 18,577.11 | 53.85 | 54.90 | 60.82 | 80.54 | 105.30 | 50.23 | 3536.41 | 13.09x | -| baseline:md_complex | 21,204.77 | 47.17 | 48.08 | 51.35 | 61.06 | 92.99 | 44.06 | 2994.40 | 11.47x | -| baseline:bash_complex | 25,712.77 | 38.89 | 39.62 | 41.19 | 53.38 | 74.29 | 36.28 | 503.38 | 9.46x | +| baseline:json_complex | 260,069.90 | 3.85 | 4.56 | 7.40 | 8.46 | 14.65 | 3.45 | 1563.51 | baseline | +| baseline:css_complex | 167,012.07 | 5.99 | 6.32 | 9.97 | 13.07 | 19.59 | 5.50 | 11815.94 | 1.56x | +| baseline:ts_complex | 19,432.01 | 51.52 | 52.43 | 55.23 | 64.98 | 94.46 | 48.46 | 4094.13 | 13.38x | +| baseline:html_complex | 65,731.39 | 15.19 | 15.67 | 17.33 | 21.50 | 31.21 | 13.96 | 1749.55 | 3.96x | +| baseline:svelte_complex | 17,331.59 | 57.70 | 58.83 | 62.36 | 71.82 | 110.52 | 54.46 | 602.01 | 15.01x | +| baseline:md_complex | 19,371.82 | 51.64 | 52.63 | 56.05 | 63.93 | 95.81 | 48.53 | 2740.51 | 13.43x | +| baseline:sh_complex | 23,532.17 | 42.47 | 43.24 | 44.49 | 48.70 | 75.43 | 40.15 | 1410.32 | 11.05x | ### Large (100x content) | Task Name | ops/sec | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best | | -------------------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -| large:json_complex | 2,223.38 | 0.45 | 0.46 | 0.49 | 0.60 | 0.80 | 0.42 | 2.14 | baseline | -| large:css_complex | 1,464.53 | 0.68 | 0.71 | 0.85 | 1.08 | 1.29 | 0.63 | 3.06 | 1.52x | -| large:ts_complex | 137.71 | 7.21 | 7.55 | 8.11 | 9.19 | 12.21 | 6.64 | 14.09 | 16.15x | -| large:html_complex | 597.91 | 1.67 | 1.77 | 2.06 | 2.20 | 2.70 | 1.56 | 3.32 | 3.72x | -| large:svelte_complex | 149.85 | 6.58 | 7.09 | 7.98 | 8.31 | 9.13 | 5.97 | 12.76 | 14.84x | -| large:md_complex | 107.60 | 9.27 | 9.72 | 11.72 | 12.13 | 12.79 | 8.67 | 14.58 | 20.66x | -| large:bash_complex | 209.69 | 4.74 | 5.03 | 6.15 | 6.88 | 7.72 | 4.43 | 10.20 | 10.60x | +| large:json_complex | 2,153.19 | 0.46 | 0.47 | 0.50 | 0.55 | 0.76 | 0.44 | 1.27 | baseline | +| large:css_complex | 1,422.42 | 0.70 | 0.72 | 0.74 | 0.83 | 1.29 | 0.67 | 1.76 | 1.51x | +| large:ts_complex | 156.82 | 6.37 | 6.48 | 6.68 | 7.25 | 10.36 | 6.15 | 11.25 | 13.73x | +| large:html_complex | 634.84 | 1.57 | 1.61 | 1.77 | 2.24 | 2.51 | 1.49 | 5.02 | 3.39x | +| large:svelte_complex | 153.08 | 6.52 | 6.73 | 7.43 | 10.55 | 11.06 | 6.18 | 12.00 | 14.07x | +| large:md_complex | 155.49 | 6.42 | 6.66 | 7.51 | 7.78 | 8.25 | 6.11 | 10.59 | 13.85x | +| large:sh_complex | 210.39 | 4.75 | 4.85 | 5.45 | 6.53 | 7.01 | 4.58 | 8.57 | 10.23x | ### Pathological (generated, 32KB) -| Task Name | ops/sec | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best | -| ------------------------------------ | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -| pathological:ts_colon_dense | 485.87 | 2.05 | 2.18 | 2.38 | 2.71 | 3.26 | 1.96 | 3.83 | 14.01x | -| pathological:ts_angle_dense | 529.38 | 1.88 | 2.01 | 2.26 | 2.54 | 3.26 | 1.74 | 4.36 | 12.86x | -| pathological:ts_paren_dense | 539.91 | 1.85 | 1.94 | 2.34 | 2.58 | 3.21 | 1.77 | 3.67 | 12.61x | -| pathological:ts_deep_template | 309.85 | 3.21 | 3.37 | 3.59 | 3.75 | 4.22 | 3.07 | 4.83 | 21.97x | -| pathological:ts_huge_line | 893.42 | 1.12 | 1.15 | 1.32 | 1.57 | 1.88 | 1.05 | 2.74 | 7.62x | -| pathological:ts_long_string | 6,258.79 | 0.16 | 0.17 | 0.22 | 0.29 | 0.34 | 0.15 | 3.82 | 1.09x | -| pathological:ts_escape_heavy | 6,806.33 | 0.15 | 0.15 | 0.18 | 0.23 | 0.29 | 0.14 | 3.99 | baseline | -| pathological:ts_many_tiny | 484.70 | 2.05 | 2.19 | 2.53 | 2.77 | 3.25 | 1.92 | 4.85 | 14.04x | -| pathological:json_deep_array | 3,027.37 | 0.33 | 0.34 | 0.38 | 0.44 | 0.59 | 0.31 | 2.38 | 2.25x | -| pathological:html_angle_flood | 1,108.13 | 0.90 | 0.93 | 1.07 | 1.30 | 1.58 | 0.82 | 2.31 | 6.14x | -| pathological:html_attr_dense | 542.58 | 1.84 | 1.90 | 2.14 | 2.57 | 2.89 | 1.70 | 4.12 | 12.54x | -| pathological:html_entity_dense | 2,179.84 | 0.46 | 0.47 | 0.50 | 0.65 | 1.04 | 0.42 | 1.77 | 3.12x | -| pathological:svelte_expression_dense | 271.40 | 3.66 | 3.83 | 4.21 | 4.56 | 4.87 | 3.42 | 5.56 | 25.08x | -| pathological:md_emphasis_dense | 2,236.90 | 0.45 | 0.46 | 0.50 | 0.59 | 0.82 | 0.43 | 2.47 | 3.04x | -| pathological:md_fence_dense | 778.02 | 1.29 | 1.33 | 1.50 | 1.69 | 2.09 | 1.20 | 11.81 | 8.75x | -| pathological:md_bracket_dense | 2,725.11 | 0.37 | 0.37 | 0.41 | 0.45 | 0.63 | 0.34 | 2.25 | 2.50x | -| pathological:md_link_paren_dense | 2,390.37 | 0.42 | 0.42 | 0.44 | 0.51 | 0.77 | 0.39 | 2.25 | 2.85x | -| pathological:svelte_attr_expr_dense | 520.96 | 1.92 | 2.00 | 2.26 | 2.37 | 2.93 | 1.81 | 3.45 | 13.07x | -| pathological:css_deep_nesting | 1,123.53 | 0.89 | 0.92 | 1.07 | 1.27 | 1.55 | 0.82 | 3.52 | 6.06x | -| pathological:bash_deep_command_sub | 468.33 | 2.14 | 2.20 | 2.43 | 2.83 | 3.14 | 2.00 | 3.93 | 14.53x | +| Task Name | ops/sec | p50 (ms) | p75 (ms) | p90 (ms) | p95 (ms) | p99 (ms) | min (ms) | max (ms) | vs Best | +| ---------------------------------------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | +| pathological:ts_colon_dense | 595.01 | 1.68 | 1.71 | 1.79 | 1.95 | 2.94 | 1.60 | 3.74 | 11.06x | +| pathological:ts_angle_dense | 624.93 | 1.60 | 1.63 | 1.76 | 1.91 | 2.94 | 1.53 | 4.47 | 10.53x | +| pathological:ts_paren_dense | 579.86 | 1.73 | 1.75 | 1.81 | 1.98 | 2.44 | 1.65 | 5.74 | 11.34x | +| pathological:ts_deep_template | 666.61 | 1.50 | 1.54 | 1.69 | 1.77 | 2.04 | 1.42 | 2.68 | 9.87x | +| pathological:ts_huge_line | 1,017.25 | 0.98 | 1.01 | 1.11 | 1.56 | 1.86 | 0.91 | 4.74 | 6.47x | +| pathological:ts_long_string | 5,299.46 | 0.19 | 0.19 | 0.20 | 0.23 | 0.32 | 0.17 | 2.56 | 1.24x | +| pathological:ts_escape_heavy | 6,578.47 | 0.15 | 0.15 | 0.16 | 0.17 | 0.22 | 0.14 | 1.58 | baseline | +| pathological:ts_many_tiny | 560.31 | 1.79 | 1.81 | 1.91 | 2.40 | 2.54 | 1.72 | 3.26 | 11.74x | +| pathological:json_deep_array | 3,137.71 | 0.32 | 0.32 | 0.34 | 0.37 | 0.58 | 0.30 | 1.80 | 2.10x | +| pathological:html_angle_flood | 1,171.54 | 0.86 | 0.87 | 0.90 | 0.99 | 1.37 | 0.82 | 1.75 | 5.62x | +| pathological:html_attr_dense | 733.30 | 1.36 | 1.39 | 1.49 | 1.92 | 2.18 | 1.29 | 2.84 | 8.97x | +| pathological:html_entity_dense | 1,959.96 | 0.51 | 0.52 | 0.53 | 0.56 | 1.02 | 0.48 | 1.53 | 3.36x | +| pathological:svelte_expression_dense | 306.70 | 3.24 | 3.34 | 3.55 | 3.66 | 3.86 | 3.07 | 4.89 | 21.45x | +| pathological:md_emphasis_dense | 2,267.50 | 0.44 | 0.45 | 0.48 | 0.51 | 0.72 | 0.42 | 2.17 | 2.90x | +| pathological:md_fence_dense | 733.96 | 1.36 | 1.38 | 1.42 | 1.60 | 1.94 | 1.32 | 2.12 | 8.96x | +| pathological:md_bracket_dense | 2,549.72 | 0.39 | 0.40 | 0.42 | 0.44 | 0.68 | 0.38 | 2.19 | 2.58x | +| pathological:md_link_paren_dense | 2,196.22 | 0.46 | 0.46 | 0.49 | 0.51 | 0.83 | 0.44 | 2.45 | 3.00x | +| pathological:svelte_attr_expr_dense | 464.01 | 2.15 | 2.20 | 2.38 | 2.44 | 2.77 | 2.06 | 4.17 | 14.18x | +| pathological:svelte_tag_only_dense | 673.00 | 1.49 | 1.52 | 1.65 | 2.35 | 2.58 | 1.39 | 3.78 | 9.77x | +| pathological:html_no_entity_dense | 664.61 | 1.51 | 1.54 | 1.63 | 1.93 | 2.53 | 1.42 | 2.76 | 9.90x | +| pathological:md_tag_per_line | 669.58 | 1.49 | 1.52 | 1.65 | 1.98 | 2.50 | 1.41 | 3.77 | 9.82x | +| pathological:md_link_paren_per_line | 1,676.69 | 0.60 | 0.60 | 0.63 | 0.64 | 0.80 | 0.57 | 2.21 | 3.92x | +| pathological:css_deep_nesting | 1,168.69 | 0.86 | 0.87 | 0.92 | 1.04 | 1.29 | 0.81 | 1.69 | 5.63x | +| pathological:bash_deep_command_sub | 1,077.93 | 0.93 | 0.95 | 1.03 | 1.19 | 1.78 | 0.88 | 2.32 | 6.10x | +| pathological:ts_template_full_depth | 580.57 | 1.72 | 1.78 | 2.91 | 3.30 | 3.54 | 1.63 | 4.74 | 11.33x | +| pathological:bash_cmdsub_full_depth | 686.52 | 1.45 | 1.52 | 2.01 | 2.70 | 3.09 | 1.35 | 4.28 | 9.58x | +| pathological:bash_heredoc_sub_full_depth | 404.31 | 2.48 | 2.51 | 2.65 | 2.93 | 3.20 | 2.39 | 3.72 | 16.27x | +| pathological:bash_heredoc_dense | 1,064.06 | 0.94 | 0.96 | 0.99 | 1.12 | 1.45 | 0.90 | 1.90 | 6.18x | +| pathological:md_deep_self_embed | 89.08 | 11.21 | 11.46 | 11.66 | 12.21 | 15.35 | 10.75 | 16.87 | 73.85x | + +### Output metrics + +Deterministic DOM-cost proxies (not timed): spans rendered and utf8 HTML bytes per case. + +| task | input chars | spans | html bytes | bytes/char | +| :-- | --: | --: | --: | --: | +| baseline:json_complex | 327 | 77 | 3175 | 9.71 | +| baseline:css_complex | 502 | 73 | 3263 | 6.50 | +| baseline:ts_complex | 2883 | 577 | 25443 | 8.83 | +| baseline:html_complex | 733 | 230 | 11416 | 15.57 | +| baseline:svelte_complex | 2517 | 680 | 30931 | 12.29 | +| baseline:md_complex | 2799 | 525 | 25681 | 9.18 | +| baseline:sh_complex | 2749 | 344 | 15497 | 5.64 | +| pathological:ts_colon_dense | 32768 | 16384 | 622592 | 19.00 | +| pathological:ts_angle_dense | 32768 | 16384 | 671744 | 20.50 | +| pathological:ts_paren_dense | 32769 | 21846 | 851994 | 26.00 | +| pathological:ts_deep_template | 32763 | 38994 | 2285772 | 69.77 | +| pathological:ts_huge_line | 32760 | 10920 | 433524 | 13.23 | +| pathological:ts_long_string | 32767 | 4 | 32911 | 1.00 | +| pathological:ts_escape_heavy | 32767 | 4 | 32911 | 1.00 | +| pathological:ts_many_tiny | 32768 | 24576 | 925696 | 28.25 | +| pathological:json_deep_array | 32767 | 3 | 32879 | 1.00 | +| pathological:html_angle_flood | 32769 | 10925 | 469775 | 14.34 | +| pathological:html_attr_dense | 32775 | 28405 | 1343775 | 41.00 | +| pathological:html_entity_dense | 32766 | 5461 | 344043 | 10.50 | +| pathological:svelte_expression_dense | 32769 | 43692 | 1758603 | 53.67 | +| pathological:md_emphasis_dense | 32766 | 0 | 32766 | 1.00 | +| pathological:md_fence_dense | 32760 | 12480 | 547560 | 16.71 | +| pathological:md_bracket_dense | 32768 | 0 | 32768 | 1.00 | +| pathological:md_link_paren_dense | 32768 | 0 | 32768 | 1.00 | +| pathological:svelte_attr_expr_dense | 32775 | 32775 | 1420250 | 43.33 | +| pathological:svelte_tag_only_dense | 32768 | 32768 | 1564672 | 47.75 | +| pathological:html_no_entity_dense | 32768 | 32768 | 1564672 | 47.75 | +| pathological:md_tag_per_line | 32775 | 28405 | 1383105 | 42.20 | +| pathological:md_link_paren_per_line | 32766 | 0 | 32766 | 1.00 | +| pathological:css_deep_nesting | 32754 | 20703 | 809580 | 24.72 | +| pathological:bash_deep_command_sub | 32893 | 16256 | 687197 | 20.89 | +| pathological:ts_template_full_depth | 32766 | 39319 | 2313244 | 70.60 | +| pathological:bash_cmdsub_full_depth | 32767 | 32766 | 1408939 | 43.00 | +| pathological:bash_heredoc_sub_full_depth | 32761 | 10920 | 589681 | 18.00 | +| pathological:bash_heredoc_dense | 32760 | 9555 | 499590 | 15.25 | +| pathological:md_deep_self_embed | 32766 | 195 | 41216 | 1.26 | + From 036113d0b9c3832d71e8b3907d675a43ffb088e4 Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Sat, 11 Jul 2026 08:19:00 -0400 Subject: [PATCH 09/15] browser benchmark data --- benchmark/results.md | 49 ++++++++++++++------------ src/routes/docs/benchmark/+page.svelte | 20 +++++------ 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/benchmark/results.md b/benchmark/results.md index e5b1caf8..6b658356 100644 --- a/benchmark/results.md +++ b/benchmark/results.md @@ -110,26 +110,29 @@ Deterministic DOM-cost proxies (not timed): spans rendered and utf8 HTML bytes p ## Browser Benchmark Results -html 90.09ms avg time 22 ops/sec 1.4% CV - -ranges 26.17ms avg time 48 ops/sec 4.8% CV +244.3% vs baseline - -| Language | Implementation | Mean (ms) | Median (ms) | Std Dev | CV | P75 (ms) | P90 (ms) | P95 (ms) | P99 (ms) | Ops/sec | Outliers | Failed | Stability | -| -------- | -------------- | --------- | ----------- | ------- | ---- | -------- | -------- | -------- | -------- | ------- | -------- | ------ | --------- | -| ts | html | 72.54 | 72.50 | 3.66 | 5.1% | 74.00 | 76.76 | 77.68 | 78.42 | 14 | 0/5 | 0 | 100% | -| ts | ranges | 37.82 | 37.70 | 2.15 | 5.7% | 39.70 | 40.24 | 40.42 | 40.56 | 26 | 0/5 | 0 | 100% | -| css | html | 13.73 | 13.70 | 0.05 | 0.3% | 13.75 | 13.78 | 13.79 | 13.80 | 73 | 2/5 | 0 | 100% | -| css | ranges | 13.80 | 13.80 | 0.00 | 0.0% | 13.80 | 13.80 | 13.80 | 13.80 | 72 | 2/5 | 0 | 100% | -| html | html | 112.80 | 112.60 | 0.53 | 0.5% | 112.88 | 113.37 | 113.54 | 113.67 | 9 | 1/5 | 0 | 100% | -| html | ranges | 15.48 | 15.00 | 1.43 | 9.2% | 15.90 | 17.16 | 17.58 | 17.92 | 65 | 0/5 | 0 | 100% | -| json | html | 188.20 | 188.20 | 0.49 | 0.3% | 188.50 | 188.68 | 188.74 | 188.79 | 5 | 2/5 | 0 | 100% | -| json | ranges | 13.72 | 13.75 | 0.08 | 0.6% | 13.80 | 13.80 | 13.80 | 13.80 | 73 | 1/5 | 0 | 100% | -| svelte | html | 98.42 | 97.80 | 1.66 | 1.7% | 98.87 | 100.27 | 100.73 | 101.11 | 10 | 1/5 | 0 | 100% | -| svelte | ranges | 38.94 | 39.80 | 2.28 | 5.9% | 40.80 | 41.10 | 41.20 | 41.28 | 26 | 0/5 | 0 | 100% | -| md | html | 54.86 | 55.00 | 0.45 | 0.8% | 55.10 | 55.34 | 55.42 | 55.48 | 18 | 0/5 | 0 | 100% | -| md | ranges | 37.24 | 35.90 | 2.79 | 7.5% | 39.30 | 40.68 | 41.14 | 41.51 | 27 | 0/5 | 0 | 100% | - -Legend CV: Coefficient of Variation (std_dev/mean) - lower is better, <15% is -good P75/P90/P95/P99: Percentiles - X% of measurements were faster than this -Ops/sec: Operations per second (throughput) Stability: Percentage of iterations -with stable system metrics +html 50.01ms avg time 40 ops/sec 2.6% CV + +ranges 7.25ms avg time 222 ops/sec 7.2% CV +590.2% vs baseline + +| Language | Implementation | Mean (ms) | Paint (ms) | Median (ms) | Std Dev | CV | P75 (ms) | P90 (ms) | P95 (ms) | P99 (ms) | Ops/sec | Outliers | Failed | Stability | +| -------- | -------------- | --------- | ---------- | ----------- | ------- | ----- | -------- | -------- | -------- | -------- | ------- | -------- | ------ | --------- | +| ts | html | 91.23 | 108.45 | 93.80 | 6.66 | 7.3% | 94.30 | 95.02 | 95.26 | 95.45 | 11 | 1/5 | 0 | 100% | +| ts | ranges | 11.80 | 55.15 | 11.20 | 1.44 | 12.2% | 12.80 | 13.40 | 13.60 | 13.76 | 85 | 0/5 | 0 | 100% | +| css | html | 10.52 | 32.62 | 10.50 | 0.26 | 2.5% | 10.60 | 10.78 | 10.84 | 10.89 | 95 | 0/5 | 0 | 100% | +| css | ranges | 2.58 | 32.42 | 2.60 | 0.11 | 4.2% | 2.60 | 2.66 | 2.68 | 2.70 | 388 | 0/5 | 0 | 100% | +| html | html | 28.24 | 32.22 | 28.50 | 0.58 | 2.0% | 28.60 | 28.72 | 28.76 | 28.79 | 35 | 0/5 | 0 | 100% | +| html | ranges | 3.96 | 32.54 | 3.90 | 0.42 | 10.5% | 4.10 | 4.40 | 4.50 | 4.58 | 253 | 0/5 | 0 | 100% | +| json | html | 10.60 | 33.10 | 10.60 | 0.39 | 3.7% | 10.70 | 11.00 | 11.10 | 11.18 | 94 | 0/5 | 0 | 100% | +| json | ranges | 1.92 | 32.82 | 1.90 | 0.27 | 14.0% | 2.10 | 2.16 | 2.18 | 2.20 | 521 | 0/5 | 0 | 100% | +| svelte | html | 91.38 | 107.83 | 92.00 | 1.06 | 1.2% | 92.50 | 108.40 | 113.70 | 117.94 | 11 | 1/5 | 0 | 100% | +| svelte | ranges | 12.52 | 78.36 | 12.80 | 0.88 | 7.0% | 13.20 | 13.32 | 13.36 | 13.39 | 80 | 0/5 | 0 | 100% | +| md | html | 69.38 | 74.98 | 69.50 | 0.70 | 1.0% | 70.30 | 75.94 | 77.82 | 79.32 | 14 | 1/5 | 0 | 100% | +| md | ranges | 9.78 | 32.20 | 9.70 | 0.20 | 2.1% | 10.00 | 10.00 | 10.00 | 10.00 | 102 | 0/5 | 0 | 100% | +| sh | html | 48.70 | 53.70 | 48.70 | 0.37 | 0.8% | 48.90 | 49.08 | 49.14 | 49.19 | 21 | 0/5 | 0 | 100% | +| sh | ranges | 8.16 | 32.54 | 8.20 | 0.05 | 0.7% | 8.20 | 8.20 | 8.20 | 8.20 | 123 | 0/5 | 0 | 100% | + +Legend Paint (ms): paint-settle time after commit, tracked separately from +work time CV: Coefficient of Variation (std_dev/mean) - lower is better, <15% +is good P75/P90/P95/P99: Percentiles - X% of measurements were faster than +this Ops/sec: Operations per second (throughput) Stability: Percentage of +iterations with stable system metrics diff --git a/src/routes/docs/benchmark/+page.svelte b/src/routes/docs/benchmark/+page.svelte index 9d2ade69..1fedcfbf 100644 --- a/src/routes/docs/benchmark/+page.svelte +++ b/src/routes/docs/benchmark/+page.svelte @@ -29,13 +29,13 @@ // from one interactive-benchmark run on a single machine — illustrative, not a // spec; run the tool for numbers on your own hardware const browser_results: Array<{lang: string; html: number; ranges: number}> = [ - {lang: 'ts', html: 56, ranges: 16}, - {lang: 'css', html: 8, ranges: 3}, - {lang: 'html', html: 18, ranges: 5}, - {lang: 'json', html: 8, ranges: 2}, - {lang: 'svelte', html: 63, ranges: 16}, - {lang: 'md', html: 43, ranges: 14}, - {lang: 'sh', html: 35, ranges: 12}, + {lang: 'ts', html: 91, ranges: 12}, + {lang: 'css', html: 11, ranges: 3}, + {lang: 'html', html: 28, ranges: 4}, + {lang: 'json', html: 11, ranges: 2}, + {lang: 'svelte', html: 91, ranges: 13}, + {lang: 'md', html: 69, ranges: 10}, + {lang: 'sh', html: 49, ranges: 8}, ]; // bars scale to the slowest single result so lengths compare across languages const browser_max = Math.max(...browser_results.flatMap((r) => [r.html, r.ranges])); @@ -92,7 +92,7 @@ >Representative stylize results on larger inputs from one machine; absolute numbers vary by hardware. See the committed comparison results for the full matrix across engines and - sizes.

@@ -100,11 +100,11 @@

The interactive benchmark ⚡ runs live in your browser to - get messy real-life results. It times fuz_code's two renderers - the standard HTML path () and the experimental CSS Custom Highlight API path (, ranges) - across every supported language, reporting mean, median, percentiles, + />, ranges) — across every supported language, reporting mean, median, percentiles, coefficient of variation, and throughput per run, with system-stability gating between samples.

From 6e015ab08e833d46be28f023154130f9afb1e85e Mon Sep 17 00:00:00 2001 From: Ryan Atkinson Date: Sat, 11 Jul 2026 08:55:11 -0400 Subject: [PATCH 10/15] docs --- .changeset/plain-peas-cough.md | 2 +- CLAUDE.md | 32 ++++++++--------- README.md | 46 ++++++++++++------------ benchmark/results.md | 18 +++++----- src/lib/Code.svelte | 35 ++++++------------- src/lib/CodeHighlight.svelte | 48 +++++++++----------------- src/lib/lexer_bash.ts | 10 +++--- src/lib/lexer_md.ts | 2 +- src/routes/+page.svelte | 6 ++-- src/routes/benchmark/+page.svelte | 1 + src/routes/docs/benchmark/+page.svelte | 46 ++++++++++++------------ src/routes/docs/samples/+page.svelte | 2 +- src/routes/docs/usage/+page.svelte | 25 +++++--------- src/test/fixtures/check.test.ts | 13 ++----- 14 files changed, 120 insertions(+), 166 deletions(-) diff --git a/.changeset/plain-peas-cough.md b/.changeset/plain-peas-cough.md index 8c3e3b81..05972ef2 100644 --- a/.changeset/plain-peas-cough.md +++ b/.changeset/plain-peas-cough.md @@ -2,4 +2,4 @@ '@fuzdev/fuz_code': minor --- -refactor make `sh` the primary and `bash` the alias +make `sh` the primary shell language id, with `bash` and `shell` as aliases diff --git a/CLAUDE.md b/CLAUDE.md index 1143fc45..5105a2bb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,7 +5,7 @@ fuz_code (`@fuzdev/fuz_code`) is a runtime syntax highlighting library optimized for HTML generation with CSS classes. It originated as a PrismJS fork, but the tokenizer is a full rewrite — one hand-written single-pass lexer per language -emitting a flat token event stream, with zero regular expressions. +emitting a flat token event stream, without regular expressions. For coding conventions, see Skill(fuz-stack). @@ -72,7 +72,9 @@ src/ │ ├── lexer_*.ts # hand-written lexers (json, ts, css, bash, markup, svelte, md) │ ├── Code.svelte # main Svelte component │ ├── CodeHighlight.svelte # experimental CSS Highlight API +│ ├── CodeTextarea.svelte # experimental live-highlighted textarea │ ├── highlight_manager.ts # CSS Highlight API manager +│ ├── range_highlighting.svelte.ts # shared range-highlighting helper │ ├── highlight_priorities.ts # generated token priorities │ ├── theme.css # token CSS classes │ ├── theme_variables.css # CSS variable fallbacks @@ -88,9 +90,9 @@ src/ │ ├── check.test.ts # fixture validation │ └── update.task.ts # fixture regeneration task └── routes/ # demo/docs site - ├── samples/ # language samples showcase + ├── samples/ # shared sample data (all.gen.ts) ├── benchmark/ # interactive benchmark UI - └── docs/ # API documentation + └── docs/ # tomes: usage, samples, textarea, benchmark, api ``` ### Core system @@ -162,11 +164,13 @@ can't overflow the call stack; past the cap a region stays plain text. **Code.svelte props:** -- `content` (required) - source code to highlight -- `lang` - language identifier (default: 'svelte') +- `content` - source code to highlight (or pass `dangerous_raw_html` from the + preprocessor instead) +- `lang` - language identifier (default: 'svelte'; `null` disables highlighting) - `inline` - boolean for inline vs block - `wrap` - boolean for text wrapping - `nomargin` - boolean for margin control +- `syntax_styler` - custom `SyntaxStyler` (default: `syntax_styler_global`) ## Supported languages @@ -214,8 +218,8 @@ engines). ### Updating committed result snapshots `benchmark/compare/results.md` is linked from `README.md` as evidence for -the "vastly faster than Shiki" claim — it's load-bearing for the public -narrative, not just incidental output. `benchmark/results.md` is referenced +the "about two orders of magnitude faster" claim vs Shiki — it's load-bearing +for the public narrative, not just incidental output. `benchmark/results.md` is referenced locally as a perf baseline. `benchmark/baseline.json` is the machine-readable counterpart used by `benchmark_baseline_compare` for regression detection. It's **gitignored** (per the fuz_util/fuz_ui convention) — perf numbers @@ -223,15 +227,6 @@ vary across machines, so the baseline is a per-developer local tracker, re-seeded on each machine that runs benchmarks. PR review still relies on the committed `results.md`. -Workflow: - -```bash -npm run benchmark # check current perf against your local baseline -npm run benchmark:save # accept the change: rewrites results.md + baseline.json -npm run benchmark:clean # nuke the local baseline (re-seed on next --save) -npm run benchmark:vs:write # full overwrite of benchmark/compare/results.md -``` - `--save` is a single switch by design — accepting a perf change should update both the doc artifact and the local regression baseline atomically, so they can't drift apart. @@ -269,6 +264,7 @@ If the baseline schema version in `@fuzdev/fuz_util` advances, a stale highlighting. Limited browser support - use `Code.svelte` for production. - `mode` prop: 'auto', 'ranges', or 'html' +- `CodeTextarea.svelte` - editable `
languagefuz_code vs Prism fuz_code vs Shiki
{lang}{factor}{vs_prism}{vs_shiki}