npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lumis-sh/lumis

v0.4.0

Published

Syntax Highlighter powered by Tree-sitter and Neovim themes

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/lumis

Themes are published separately:

npm install @lumis-sh/themes

Quick 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 loaded

Language 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/lumis ships the JS API and embedded web-tree-sitter runtime WASM.
  • Language parsers are separate versioned .wasm assets 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 as 0.26, which CDNs resolve to the latest compatible patch release.
  • In Node, fetched parser WASM files are cached under node_modules/.cache/lumis when possible.
  • In restricted or offline environments, set a custom resolver before calling highlight() or createHighlighter().

Output Formats

  • htmlInline() for self-contained HTML with inline styles
  • htmlLinked() for class-based HTML that uses external CSS
  • htmlMultiThemes() for light/dark or multi-theme HTML with CSS variables
  • terminal() for ANSI-colored terminal output
  • bbcodeScoped() 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]"&#91;url=x&#93;"[/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'