fuz_code

syntax styler for TypeScript, Svelte, Markdown, and more 🎨

repo 0.54.0 npm

npm i -D @fuzdev/fuz_code

usage #

fuz_code tokenizes code with one lexer per language, without regular expressions, and generates HTML with .token_* classes for a theme to style. The Code Svelte component is the typical entry point.

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

// +layout.svelte import '@fuzdev/fuz_code/theme.css'; // add this

then use Code:

<script> // Something.svelte import Code from '@fuzdev/fuz_code/Code.svelte'; </script> <Code content="<header>hello world</header>" />

outputs:

<header>hello world</header>

Dependencies
#

The default theme reads color-scheme-aware color variables from fuz_css; see its usage docs for setup. Without fuz_css, import theme_variables.css or define the variables yourself:

// +layout.svelte import '@fuzdev/fuz_code/theme.css'; // if not using fuz_css, add this too: import '@fuzdev/fuz_code/theme_variables.css';

Preprocessor
#

The svelte_preprocess_fuz_code preprocessor styles static Code content at build time:

// vite.config.ts import {defineConfig} from 'vite'; import {sveltekit} from '@sveltejs/kit/vite'; import {vitePreprocess} from '@sveltejs/vite-plugin-svelte'; import {svelte_preprocess_fuz_code} from '@fuzdev/fuz_code/svelte_preprocess_fuz_code.ts'; export default defineConfig({ plugins: [ sveltekit({ preprocess: [ svelte_preprocess_fuz_code(), vitePreprocess(), ], }), ], });

A content that is statically known (a string, a module-level constant, or a ternary of those) is replaced with pre-rendered HTML. Anything else is left for runtime styling.

Svelte support
#

The Svelte lexer was originally based on prism-svelte by @pngwn (pngwn.at). Code styles Svelte by default:

<Code content="<scr..." />

styled:

<script lang="ts"> import Card from '@fuzdev/fuz_ui/Card.svelte'; console.log('hello Card', Card); </script> <Card> <div class="greeting">hi {friend}</div> </Card>

TypeScript support
#

Code supports TypeScript with lang="ts":

<Code lang="ts" content="export type A<T> = ('b' | 3) & T;" />
export type A<T> = ('b' | 3) & T;

Markdown support
#

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

<Code lang="md" content="# hello `world` ..." />
# hello `world` ```ts const a = 1; ```

Disabling styling
#

Passing lang={null} disables syntax styling:

<Code lang={null} content="<aside>all is gray</aside>" /> <aside>all is gray</aside>

The plaintext language does the same by name. Every SyntaxStyler registers it, so it also works with stylize:

<Code lang="plaintext" content="<aside>all is gray</aside>" /> <aside>all is gray</aside>

A lang that isn't registered also renders as plain text in Code, and logs an error in development. Called directly, stylize and lex throw on an unregistered language, so check with has_lang first or fall back to plaintext:

const html = syntax_styler_global.stylize( text, syntax_styler_global.has_lang(lang) ? lang : 'plaintext' );

Layout
#

Code is a block by default:

abc
<div>ab<Code content="c" /></div>

It can be inlined with <Code inline content="..." />

Programmatic usage
#

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

import {syntax_styler_global} from '@fuzdev/fuz_code/syntax_styler_global.ts'; const html = syntax_styler_global.stylize('const x: number = 42;', 'ts');

returns an HTML string:

<span class="token_keyword">const</span> x<span class="token_type_annotation"><span class="token_:">:</span> <span class="token_type"><span class="token_builtin">number</span></span></span> <span class="token_operator">=</span> <span class="token_number">42</span><span class="token_punctuation">;</span>

then rendered with:

<code data-lang="ts">{@html programmatic_result}</code>

renders:

const x: number = 42;

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

import {SyntaxStyler} from '@fuzdev/fuz_code/syntax_styler.ts'; import {lexer_markup} from '@fuzdev/fuz_code/lexer_markup.ts'; import {lexer_css} from '@fuzdev/fuz_code/lexer_css.ts'; const styler = new SyntaxStyler(); styler.add_lang(lexer_markup); styler.add_lang(lexer_css); const html = styler.stylize('<div class="example">hello</div>', 'html');

Experimental highlighting API
#

fuz_code also has experimental support for the CSS Custom Highlight API with CodeHighlight — see the samples and textarea.

samples #

Syntax-styled samples in every supported language, rendered with Code and the experimental CodeHighlight.

textarea #

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

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

benchmark #

fuz_code is optimized for runtime syntax styling. This page summarizes how it compares with Prism and Shiki, measures its two renderers in the browser, and lists the benchmarks that run from a checkout. The comparisons come from syntax-highlighter-bench, a separate suite that times each library on the same neutral inputs.

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. For static content, the svelte_preprocess_fuz_code preprocessor renders at build time. Languages it lacks can be added by writing a lexer.

Compared to Shiki and Prism
#

syntax-highlighter-bench measures fuz_code against Prism, Shiki (both its JavaScript and Oniguruma engines), and Twinkleplop, each in its own process, on real code none of them authored. Highlighting to HTML, fuz_code runs about an order of magnitude faster than Prism and one to two orders of magnitude faster than Shiki:

languagefuz_code vs Prismfuz_code vs Shiki
ts~12x~91x
js~8.7x~103x
css~6.6x~59x
html~6.7x~57x
json~6.0x~33x
svelte~8.0x~123x
md~13x~25x
sh~16x~73x
rust~6.0x~46x

Run of 2026-10-12 at commit 340492a: fuz_code 0.52.1, Prism 1.30.0, Shiki 4.5.0, Node 24.14.1, AMD Ryzen 5 PRO 7530U.

In the browser
#

The in-browser benchmark measures real DOM rendering rather than pure compute. It times fuz_code's two renderers — the standard HTML path (Code) and the experimental CSS Custom Highlight API path (CodeHighlight, ranges) — across the sample languages, reporting mean, median, percentiles, coefficient of variation, and throughput, and it pauses longer between samples when the system looks unstable.

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 run a minor collection between samples.

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

Code (html) CodeHighlight (ranges)
ts Code 91 ms 1.00x
ts CodeHighlight 12 ms 7.73x
css Code 11 ms 1.00x
css CodeHighlight 2.6 ms 4.08x
html Code 28 ms 1.00x
html CodeHighlight 4.0 ms 7.13x
json Code 11 ms 1.00x
json CodeHighlight 1.9 ms 5.52x
svelte Code 91 ms 1.00x
svelte CodeHighlight 13 ms 7.30x
md Code 69 ms 1.00x
md CodeHighlight 9.8 ms 7.09x
sh Code 49 ms 1.00x
sh CodeHighlight 8.2 ms 5.97x

Run of 2026-07-11, before the tool added Rust.

On your machine
#

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:cost — the cost model for iterating on a lexer: per-construct costs and each language's time over a grammar-free floor lexer, in a few seconds.

The internal suite includes a pathological group of adversarial inputs (deeply nested and degenerate source) that tracks the lexer's worst-case cost; the same generators back the linearity tests.

Design
#

  • Lexers scan char codes and use native indexOf and keyword maps in place of regular expressions.
  • One single-pass lexer per language emitting a flat Int32Array event stream, rendered to HTML in a single forward pass.
  • Delimiter scans are amortized linear, with no grammar interpreter or backtracking. A test suite checks that lexing time stays near-linear on adversarial inputs.
  • The lexers and styler import nothing outside the package.

The engine keeps no resumable state, so it re-lexes the whole document on every change rather than tokenizing incrementally. In the committed results the largest inputs, up to a few hundred kilobytes of source, take under about ten milliseconds in Node.

See usage for the API and samples for output in every language.

api #

Browse the full api docs.