@lumis-sh/lumis
v0.4.0
Published
Syntax Highlighter powered by Tree-sitter and Neovim themes
Maintainers
Readme
@lumis-sh/lumis
Syntax Highlighter powered by Tree-sitter and Neovim themes.
JavaScript/TypeScript package for Lumis. Works in Node.js, Bun, Deno, and browsers.
Uses web-tree-sitter for parsing.
Parser .wasm files are runtime assets. By default, Lumis fetches versioned parser WASM packages from jsDelivr on demand and caches them locally in Node.
Install
npm install @lumis-sh/lumisThemes are published separately:
npm install @lumis-sh/themesQuick Start
The fastest way to highlight code. The stateless highlight() function initializes the parser, loads the language, and returns HTML in a single async call.
import { highlight } from '@lumis-sh/lumis'
import { htmlInline } from '@lumis-sh/lumis/formatters'
import javascript from '@lumis-sh/lumis/langs/javascript'
import dracula from '@lumis-sh/themes/dracula'
const html = await highlight(
'const x = 1',
htmlInline({ language: javascript, theme: dracula })
)highlight() uses a shared default runtime. It is convenient for one-off calls, but loaded languages and WASM resolver configuration are shared process-wide.
Reuse A Highlighter
Use createHighlighter() when you want to preload languages and highlight synchronously after setup.
import { createHighlighter } from '@lumis-sh/lumis'
import { htmlInline } from '@lumis-sh/lumis/formatters'
import javascript from '@lumis-sh/lumis/langs/javascript'
import dracula from '@lumis-sh/themes/dracula'
const hl = await createHighlighter({ languages: [javascript] })
// hl.highlight() is synchronous, languages are already loaded
const html = hl.highlight(
'const x = 1',
htmlInline({ language: javascript, theme: dracula })
)Use createHighlighter() when you want explicit control over loaded languages, isolation between highlighters, or better throughput for repeated calls.
Bundles
Bundles group languages into sets. Each language loads lazily on first use, so registering a bundle with 110+ languages costs almost nothing upfront.
Five preset bundles ship with lumis:
| Bundle | Use case |
|--------|----------|
| bundles/web | Core web languages: HTML, CSS, JavaScript, TypeScript, TSX, and JSON |
| bundles/web-extra | Web frameworks, templating, and adjacent formats like Angular, Astro, Dart, Elm, HEEx, Prisma, Surface, Vue, Svelte, PHP, GraphQL, and XML |
| bundles/system | C, C++, Rust, Go, Zig, ASM, LLVM, CMake, Make |
| bundles/backend | Backend languages like C#, Elixir, Erlang, Go, Java, JS, Kotlin, PHP, Python, Ruby, Rust, Scala, SQL, TypeScript, Protobuf, and Javadoc |
| bundles/full | Every supported language |
Using a bundle
Pass the bundle to createHighlighter(). Languages register lazily and load on first loadLanguage() call.
import { createHighlighter } from '@lumis-sh/lumis'
import { htmlInline } from '@lumis-sh/lumis/formatters'
import { bundledLanguages } from '@lumis-sh/lumis/bundles/web'
import dracula from '@lumis-sh/themes/dracula'
const hl = await createHighlighter({ languages: [bundledLanguages] })
// Load a language before highlighting
await hl.loadLanguage('javascript')
const html = hl.highlight(
'const x = 1',
htmlInline({ language: 'javascript', theme: dracula })
)Using bundle handles
Each entry in a bundle is a LazyLanguage handle. You can pass it to formatters and loadLanguage() instead of using strings.
import { bundledLanguages } from '@lumis-sh/lumis/bundles/web'
const hl = await createHighlighter({ languages: [bundledLanguages] })
await hl.loadLanguage(bundledLanguages.javascript)
const html = hl.highlight(
'const x = 1',
htmlInline({ language: bundledLanguages.javascript, theme: dracula })
)Mixing bundles and individual languages
createHighlighter() accepts any combination of Language objects, bundles, and dynamic imports.
import { createHighlighter } from '@lumis-sh/lumis'
import { bundledLanguages } from '@lumis-sh/lumis/bundles/system'
import elixir from '@lumis-sh/lumis/langs/elixir'
const hl = await createHighlighter({
languages: [
elixir, // loaded immediately
bundledLanguages, // registered lazily
import('@lumis-sh/lumis/langs/python'), // loaded immediately via dynamic import
],
})Local WASM bundle packages
Install a @lumis-sh/wasm-bundle-* package when you want the matching bundle's parser WASM files available locally.
- In Node.js, installing the package is enough. Lumis will detect the installed
@lumis-sh/wasm-*parser packages automatically. - In browser bundlers, pair it with
withWasmBundle()so the bundler can include the static WASM imports.
import { createHighlighter, withWasmBundle } from '@lumis-sh/lumis'
import { bundledLanguages } from '@lumis-sh/lumis/bundles/web'
import { bundledWasms } from '@lumis-sh/wasm-bundle-web'
const languages = withWasmBundle(bundledLanguages, bundledWasms)
const hl = await createHighlighter({ languages: [languages] })Checking registered vs loaded languages
const hl = await createHighlighter({ languages: [bundledLanguages] })
hl.registeredLanguages // all languages in the bundle (including lazy)
hl.languages // only languages that have been loadedLanguage References
Formatters and hl.highlight() accept several forms for specifying a language.
import json from '@lumis-sh/lumis/langs/json'
import { bundledLanguages } from '@lumis-sh/lumis/bundles/web'
// Language object
hl.highlight(code, htmlInline({ language: json, theme }))
// LazyLanguage handle from a bundle
hl.highlight(code, htmlInline({ language: bundledLanguages.json, theme }))
// String ID (must be loaded or registered in a bundle)
hl.highlight(code, htmlInline({ language: 'json', theme }))All three are equivalent at highlight time. The runtime resolves the language by its ID.
Runtime WASM Behavior
@lumis-sh/lumisships the JS API and embeddedweb-tree-sitterruntime WASM.- Language parsers are separate versioned
.wasmassets loaded at runtime. - By default, Lumis resolves parser WASM from
https://cdn.jsdelivr.net/npm/@lumis-sh/wasm-<parser-name-without-tree-sitter-prefix>@<tree-sitter-version>/<parser>.wasm. - The
<tree-sitter-version>segment is a partial version such as0.26, which CDNs resolve to the latest compatible patch release. - In Node, fetched parser WASM files are cached under
node_modules/.cache/lumiswhen possible. - In restricted or offline environments, set a custom resolver before calling
highlight()orcreateHighlighter().
Output Formats
htmlInline()for self-contained HTML with inline styleshtmlLinked()for class-based HTML that uses external CSShtmlMultiThemes()for light/dark or multi-theme HTML with CSS variablesterminal()for ANSI-colored terminal outputbbcodeScoped()for nested BBCode tags using highlight scope names- custom formatter objects via
@lumis-sh/lumis/formatters
import { bbcodeScoped } from '@lumis-sh/lumis/formatters'
import javascript from '@lumis-sh/lumis/langs/javascript'
const output = hl.highlight('const x = "[url=x]"', bbcodeScoped({ language: javascript }))
// [keyword-javascript]const[/keyword-javascript] x = [string-javascript]"[url=x]"[/string-javascript]bbcodeScoped() emits highlight scope names as tags, not standard forum-style BBCode like [b], [color], or [code].
Custom Formatters
A formatter is an object with language and format(source). Inside format(), call the sync free functions highlightIter (for flat token callbacks) or highlightEvents (for nested open/close events) imported from @lumis-sh/lumis. Built-in formatters are regular objects. Custom ones work the same way.
Minimal example that wraps each token in a colored <span>:
import { createHighlighter, highlightIter } from '@lumis-sh/lumis'
import type { Formatter } from '@lumis-sh/lumis/formatters'
import { openPreTag, openCodeTag, closingTags, spanInline } from '@lumis-sh/lumis/formatters/html'
import rust from '@lumis-sh/lumis/langs/rust'
import dracula from '@lumis-sh/themes/dracula'
const hl = await createHighlighter({ languages: [rust] })
const formatter: Formatter = {
language: rust,
format(source) {
const parts: string[] = []
parts.push(openPreTag({ theme: dracula }))
parts.push(openCodeTag(this.language))
highlightIter(source, this.language, dracula, (text, language, _range, scope, _style) => {
if (scope) {
parts.push(spanInline(text, { language, scope, theme: dracula }))
} else {
parts.push(text)
}
})
parts.push(closingTags())
return parts.join('')
},
}
const html = hl.highlight('fn main() {}', formatter)HTML helpers (@lumis-sh/lumis/formatters/html):
import {
escape, escapeBraces,
openPreTag, openCodeTag, closePreTag, closeCodeTag, closingTags,
wrapLine, scopeToClass, textDecoration,
spanInline, spanInlineAttrs, spanLinked, spanLinkedAttrs,
spanMultiThemes, spanMultiThemesAttrs,
} from '@lumis-sh/lumis/formatters/html'ANSI helpers (@lumis-sh/lumis/formatters/ansi):
import { hexToRgb, paint, rgbToAnsi, styleToAnsi } from '@lumis-sh/lumis/formatters/ansi'Custom WASM Resolution
Override the resolver when you want to serve parser WASM files yourself, switch CDNs, or avoid network fetches in locked-down environments:
import { configureWasmResolver } from '@lumis-sh/lumis'
configureWasmResolver((_language, wasm) =>
`https://unpkg.com/${wasm.packageName}@${wasm.version}/${wasm.name}.wasm`
)This can be called at any time. It applies to highlight(), createHighlighter(), and any existing highlighter instances.
For advanced use cases that need isolated resolution (e.g., tests, multiple CDNs), pass wasmResolver directly to createHighlighter().
htmlLinked() CSS
htmlLinked() emits semantic classes instead of inline styles. Include a theme stylesheet on the page, for example:
import '@lumis-sh/themes/css/dracula.css'