NimblePublisher
Use Lumis in NimblePublisher with MDEx through a custom HTML converter.
NimblePublisher ships with Makeup-based highlighting, but it also exposes a custom html_converter hook. That is the right integration point for Lumis.
Use MDEx as the markdown engine inside that converter. This is cleaner than post-processing HTML, and it keeps syntax highlighting inside the markdown renderer where it belongs.
Keep highlighters empty
Do not configure NimblePublisher's built-in highlighters when Lumis owns code blocks.
use NimblePublisher,
build: Post,
from: Application.app_dir(:my_app, "priv/posts/**/*.md"),
as: :posts,
html_converter: MyApp.Blog.Markdown,
highlighters: []Add dependencies
def deps do
[
{:nimble_publisher, "~> 1.0"},
{:mdex, "~> 0.14"},
{:lumis, "~> 0.10"},
{:lumis_wasm_elixir, "~> 0.26.0"},
{:lumis_wasm_bundle_web, "~> 0.1"}
]
endMDEx takes Lumis as an optional dependency, so :lumis has to be declared — it
raises without it — and :mdex_native has to be told which highlighter to
download before it compiles:
# config/config.exs
import Config
config :mdex_native, syntax_highlighter: :lumisMDEx reads lumis_wasm_* parser packages. Add one per language your posts use,
or a bundle for a set of them. A fence whose language is not installed renders
as plain text. The language catalog lists the Hex
package names.
For upgrades from MDEx releases with compiled-in grammars, see MDEx parser packages.
Custom converter
NimblePublisher converts posts while MyApp.Blog compiles, and mix compile
starts no applications. Starting :mdex brings up :mdex_native, whose
start/2 is what hands its NIF the lumis_wasm_* directories to load parsers
from, so a converter has to do it. Skip it and every fence still renders through
the formatter — Lumis markup, theme background, one line span — around
plain, uncolored code, which reads as a theme problem rather than a missing
parser.
defmodule MyApp.Blog.Markdown do
def convert(_path, body, _attrs, _opts) do
{:ok, _} = Application.ensure_all_started(:mdex)
MDEx.to_html!(body,
syntax_highlight: [
engine: :lumis,
opts: [formatter: {:html_inline, theme: "catppuccin_latte"}]
]
)
end
endThat also means there is nothing to warm at boot for these posts: they were
highlighted during mix compile, before start/2 ever runs.
Lumis.Languages.async_load/1 from your application's start/2 is for Markdown
you render per request. Both sides share the cache of compiled parsers, so the
build leaves a language warm for the runtime when that cache persists between
them.
Multi-theme version
MDEx.to_html!(body,
syntax_highlight: [
engine: :lumis,
opts: [
formatter:
{:html_multi_themes,
themes: [
light: "catppuccin_latte",
dark: "catppuccin_frappe"
],
default_theme: "light-dark()"}
]
]
)