highlight.cr
highlight.cr
A Crystal port of highlight.js 11.12.0 — syntax highlighting with byte-for-byte identical HTML output to the original JavaScript library. No external dependencies.
Parity is verified by an oracle test suite: 96/96 test cases produce exactly the same HTML and relevance scores as the vendored highlight.js 11.12.0 (crystal spec spec/oracle_spec.cr).
Since the emitted markup is identical (same hljs-* classes, same structure), any highlight.js CSS theme works as-is.
Installation
Add this to your application's shard.yml:
dependencies:
highlight_cr:
github: OrelSokolov/highlight.cr
Usage
require "highlight_cr"
result = Highlight.highlight("puts 'hello'", "ruby")
puts result.value # => puts <span class="hljs-string">'hello'</span>
puts result.relevance # => 1
# Auto-detection
auto = Highlight.highlight_auto("def foo; end")
puts auto.language # => "ruby"
# Custom instance with only the languages you need
hljs = Highlight::HLJS.new
hljs.register_language("ruby")
result = hljs.highlight(code, "ruby")
What is ported from the original library
| highlight.js feature | highlight.cr | Notes |
|---|---|---|
Highlighting engine (mode compiler, subLanguage, continuations, relevance, illegal handling) |
✅ Ported | Full port of lib/core.js |
| HTML emitter (tiered scope class names, sublanguages) | ✅ Ported | Byte-identical output |
Auto-detection (highlightAuto, secondBest, tie-breaking by registration order and superset rules) |
✅ Ported | |
| Common bundle — all 36 common languages | ✅ Ported | See list below |
Language aliases (registerAliases) |
✅ Ported | |
Plugins (before:highlight, after:highlight) |
✅ Ported | |
configure (classPrefix, languages) |
✅ Ported | |
Token stream (Highlight.tokens) |
✅ Ported | Flat {scope, text} pairs for GUI consumers |
DOM API (highlightElement, highlightAll, etc.) |
❌ Not ported | highlight.cr is a pure server-side library; there is no DOM in Crystal |
| Web Worker / browser builds | ❌ Not ported | N/A for Crystal |
__emitTokens-style languages |
❌ Not ported | None exist in the common bundle |
| Languages beyond the common bundle | ❌ Not ported | Can be ported following the same scheme (see CONVENTIONS.md) |
| CSS themes | ➖ Reused | Use any highlight.js theme; markup is identical |
Languages (36)
xml, bash, c, cpp, csharp, css, markdown, diff, ruby, go, graphql, ini, java, javascript, json, kotlin, less, lua, makefile, perl, objectivec, php, php-template, plaintext, python, python-repl, r, rust, scss, shell, sql, swift, yaml, typescript, vbnet, wasm
Intentional differences from the JS original
- Byte indices: the engine operates on byte offsets (
byte_begin,byte_slice) where the original uses UTF-16 code units; results match on all oracle snippets. - Regexes: JS regexes are translated to PCRE2 by
JSRegex.translate—\w/\d/\b/\sare expanded to JS (non-Unicode) semantics,.becomes[^\n\r\u2028\u2029],\uXXXXbecomes\x{...}. - Typing:
relevanceisInt32 | Float64(integers render without.0); JSnulloverrides ininheritare expressed viaclear_*flags.
Development
crystal spec spec/oracle_spec.cr # run the 96 oracle cases
node oracle/cases.js > oracle/expected.yml # regenerate expectations
crystal run spec/diff_dbg.cr -- <lang> <snippet> # find first output divergence
Architecture notes live in PLAN.md, porting conventions in CONVENTIONS.md.
highlight.cr
- 0
- 0
- 0
- 1
- 0
- about 5 hours ago
- October 10, 2026
Sat, 10 Oct 2026 07:42:48 GMT