Three reference files, each stating whether a fact was measured, read from source, or asserted — an unattributed number is a rumour. math-on-the-web: MathML is the only no-JS route, the one pure-Go TeX→MathML library is an untagged 2023 commit, and writing MathML directly costs nothing since raw HTML renders. syntax-highlighting-choices: chroma is two modules and ~5MB, every alternative a search returns is JavaScript, and custom lexers load from XML at runtime without a rebuild. goldmark-behaviours: the seven surprises that caused or nearly caused defects — strikethrough claiming a single tilde, delimiter runs pairing across whitespace, per-parse heading id counters, footnote id prefixes, raw HTML being dropped rather than escaped, the language class already in the output, and ParseFS globbing.
1.7 KiB
Syntax highlighting, and what it costs
Established 2026-08-02: module footprint and binary sizes measured by building a probe module, the alternatives surveyed by search. Numbers are for chroma v2.27.0 on darwin/arm64.
There is one mature pure-Go option
chroma. Every "alternative to chroma" a search returns — Prism, highlight.js, Rainbow — is JavaScript, and
therefore unavailable to a theme that ships no script. zyedidia/highlight exists but needs syntax files
supplied per language and covers far less.
What chroma costs
- Two modules:
github.com/alecthomas/chroma/v2andgithub.com/dlclark/regexp2/v2. Nothing else. - ~5MB of binary. A probe importing lexers, formatters and styles built at 7.4MB against a 2.4MB baseline; khosra itself went from ~15MB to ~19MB.
- chroma is larger than khosra.
What it gives beyond colour
html.WithLineNumbers, html.BaseLineNumber(n) for a custom starting number, and
html.HighlightLines([][2]int). With html.WithClasses(true) it emits token classes and no inline colour,
so the palette stays in a stylesheet the theme owns.
Custom lexers need no rebuild
chroma.Unmarshal(data []byte) (*RegexLexer, error) reads a lexer from XML — the same format chroma's own
lexers ship in, under lexers/embedded/*.xml. With chroma.NewLexerRegistry() a site root could carry its
own language definition and register it at startup. Not built; the door is open by construction.
What chroma does not give
A filename or title on a block, and content read from a file. Those are the engine's, whatever highlights. So a design that parses the fence's info string works identically with chroma, without it, or after it.