Declaring parsers
Parsers are dependencies. How each runtime declares the languages it may load, and why nothing is fetched at runtime.
A parser is a dependency. You add it, your package manager pins it, and it is there — or you did not add it, and it is not.
Declaring parsers makes output reproducible. The set is in a file you already review, pinned by a lock you already have, so every machine renders the same document with the same parsers, and the lock records which parser produced it.
For an Elixir application that downloaded parsers before 0.9, see Upgrading to 0.9.
One rule, one place to look
Every runtime resolves parsers the same way: only the ones the project installed, with no fallback that fetches anything else. A parser is a dependency, declared where that runtime's dependencies already live.
| Runtime | Declares its parsers in | Pinned by |
|---|---|---|
| Rust | Cargo.toml features | Cargo.lock |
| JavaScript / TypeScript | package.json dependencies | your package manager's lockfile |
| JavaScript in a browser | the packages it imports | the lockfile behind your bundle |
| Elixir | mix.exs dependencies | mix.lock |
| CLI | nothing | — |
Rust links parsers statically. JavaScript installs @lumis-sh/wasm-*, and a
browser, which has no project to read, declares by what it imports. Elixir
depends on lumis_wasm_*, whose priv/parsers holds the bytes — so they travel
into a release like any other dependency's assets, with no data directory to
prepare and nothing to copy.
The CLI is the exception, deliberately. It has no project and downloads what it needs, because a viewer is not the application whose output a declaration governs, and a highlighter that behaved differently depending on the directory you ran it in would be worse than one that does not.
Add the parsers you want to mix.exs. There are no Lumis-specific commands:
mix deps.get, mix deps.update and mix.lock already are them.
defp deps do
[
{:lumis, "~> 0.10"},
{:lumis_wasm_rust, "~> 0.26.0"},
{:lumis_wasm_bundle_web, "~> 0.1"}
]
endA bundle is a package whose dependencies are its members, so it widens when it
gains a language and mix deps.update picks that up — a visible mix.lock
diff, not a silent change.
mix deps.update lumis_wasm_rust# remove the line from mix.exs, then
mix deps.unlock --unusedA project that vendors parsers rather than depending on them can point at the directories instead. Same layout; only how they got there differs.
config :lumis, parser_dirs: ["priv/parsers"]What a language you did not add does
Naming a language explicitly is your decision, so one you never depended on is an error rather than a silent download:
Lumis.Languages.load("haskell")
#=> {:error, :not_installed}A language an injection names is content rather than a decision — a fenced block in a README, say — so that block renders plain and the document is fine. A missing parser costs one block, not the page.
Releases
Nothing to prepare. Parsers are dependencies, so mix release carries them the
way it carries any dependency's priv/:
_build/prod/rel/my_app/lib/lumis_wasm_elixir-0.26.3/priv/parsers/
elixir.lumis.json
tree-sitter-elixir-0.26.3-....wasmThe released node reads them from there. There is no data directory to stage, no copy step, and no network at boot or at render time.
config :lumis, :data_dir still has a job, a smaller one: it holds the compiled
WASM module cache, which is a speed-up rather than a source. Point it somewhere
writable and persistent and a restart skips recompiling; lose it and the first
render of each language is slower and nothing else changes.
Verification
npm and Hex check every package they fetch against its published checksum, which
your lockfile records, so a parser that arrives through a package manager is
already verified. Lumis loads it as installed, beside the lumis.json that
describes it, and refuses a package outside the Tree-sitter series this Lumis
speaks. It checks a parser against the SHA-256 in lumis.json only when it
downloads the bytes itself, through a resolver or the CLI.