Warm up parsers
Move Lumis parser downloads and compilation into application startup or a deployment step across every runtime.
Warm-up moves parser setup ahead of the first request.
Two words, one meaning everywhere
Every runtime spells warm-up with the same two verbs:
- cache puts a language on disk. It survives restarts and is shared by every native runtime pointed at the same directory.
- load caches the language and keeps it in this runtime, so nothing loads it again.
Load is cache plus keeping it. Loading writes the same files caching does — verified parser bytes, and on the native runtimes their compiled Wasmtime module — and then holds the result in memory. So inside a process that will serve, loading is always the better of the two.
Caching exists for the process that is not the one serving: an image build, a release task, a CLI run that exits. There is nothing for those to hold on to.
Compiling belongs to caching, not loading. compiled/ is a directory, so a
cache that compiles is still a cache; it is holding the language in memory that
makes a load.
| Runtime | cache | load |
|---|---|---|
| Elixir | Lumis.Languages.cache/2 | Lumis.Languages.async_load/1, load/1 |
| Server-side JavaScript | cacheLanguages() | loadLanguages() |
| Browsers / CDN | automatic, verified parser bytes only | loadLanguages(), or createHighlighter({languages}) |
| CLI | lumis languages cache | — the process exits, so there is no later to hold for |
| Rust | — parsers are selected with Cargo features and compiled into the binary | — |
| Java | — the WASM module and languages ship with lumis4j | construct Lumis at startup and retain it |
Browsers cache verified parser bytes in CacheStorage, with an IndexedDB fallback for WebKit, but never a compiled module: WebAssembly compilation belongs to the browser and cannot be persisted by Lumis.
Warm-up must not own the boot
Warm-up is an optimization, never a prerequisite: highlighting loads what a document names on demand, so an application that starts cold serves correctly the whole time it is warming. It should therefore never be able to delay a boot or fail one, and every example below is written to keep both true.
Runtime APIs and commands
Start serving, then load in the background:
import { loadLanguages } from "@lumis-sh/lumis";
await startServer();
// Not awaited, so a slow CDN delays no request. The `.catch()` is required:
// an unhandled rejection terminates the process, which is exactly the failure
// warming in the background is meant to avoid.
loadLanguages(["javascript", "html", "css"]).catch((error) => {
logger.warn({ error }, "Lumis warm-up failed; languages load on demand");
});
loadLanguages() warms the same default runtime the module-level highlight()
uses, so nothing has to be threaded through your application. It accepts catalog
names, aliases, and bundle names such as bundle-web. Every name is attempted;
if any fail it rejects with an AggregateError naming each one, after the rest
have loaded.
createHighlighter({languages}) is the same load into an instance you keep
yourself, and is what browsers use.
Caching without loading
cacheLanguages() writes the store and holds nothing, for a build or prestart
step preparing a directory the serving process will read:
await cacheLanguages(["bundle-web"], { force: true });
Await that one — failing is the point of a preparation step. {force: true}
resolves the compatible package range again and replaces valid cached files.
On the native Node addon, caching also validates the parser and queries and
persists the compiled Wasmtime module. Bun and Deno, or a Node installation
without the addon, can use the same API, but the portable web-tree-sitter
fallback cannot persist compiled modules across processes.
Store directory and precedence
The CLI, Elixir NIF, and native Node addon use the same store layout. Point them at the same writable, persistent directory when one process prepares files for another:
$LUMIS_DATA_DIR/
parsers/ # exact package metadata and verified parser WASM
compiled/ # native Wasmtime compiled modules
themes/| Host | Explicit setting | Environment fallback | Default |
|---|---|---|---|
| CLI | --data-dir /app/lumis | LUMIS_DATA_DIR | platform user data directory |
| JavaScript cache API | {directory: "/app/lumis"} | LUMIS_DATA_DIR | platform user data directory |
| Elixir | config :lumis, data_dir: "/app/lumis" | LUMIS_DATA_DIR | the Lumis application priv/lumis directory |
Elixir configuration takes precedence over the environment. The JavaScript
directory option controls where cacheLanguages() writes; also set
LUMIS_DATA_DIR for the deployed highlighter when it must read that explicit
directory.
config :lumis, data_dir: "/app/lumis"
export LUMIS_DATA_DIR=/app/lumis
Choose the warm set
Warm root languages and anything they can inject. For example, Markdown may need the languages used in fenced code blocks, and HTML may inject CSS and JavaScript. Prefer a focused list or bundle over every parser unless the application genuinely accepts every supported language.
All cache operations are safe to repeat. A normal run reuses valid files;
force: true or --force is an explicit update operation that resolves the
compatible package range again.
For the store layout, integrity checks, custom resolvers, and offline images, continue with WASM and CDN.