Skip to content

Remark / mdast adapter

Prerequisites: Document IR, Rust-authoritative architecture.

@illusions-lab/mdi-remark lets an existing unified/remark pipeline (an Astro site, a remark-based linter, a static-site generator) consume MDI documents as ordinary mdast trees, without giving remark any authority over what MDI syntax means. Concretely, and this is the entire point of this page: it does not register a micromark tokenizer for MDI. Reading its ~35-line source confirms exactly what it does:

export default function remarkMdi(this: Processor): void {
const data = this.data();
(data.toMarkdownExtensions ??= []).push(mdiToMarkdown());
this.use(remarkGfm);
this.use(remarkFrontmatter, ["yaml"]);
(this as unknown as { parser: (source: string) => Root }).parser = (source) => {
const tree = toMdast(parse(source).document);
resolveFrontmatter(tree);
return tree;
};
}

It replaces Parser entirely with a function that calls the real Rust parse() from @illusions-lab/mdi and reshapes the result into mdast. remark-gfm and remark-frontmatter are used only for their mdast→Markdown stringify handlers (needed for round-tripping back to text); their own parsing hooks are registered but never reached, because this adapter’s parser function runs instead of remark’s normal parse phase.

complete .mdi source
@illusions-lab/mdi parse() ── the same Rust call every other binding makes
Rust document IR ──▶ toMdast() ──▶ mdast tree ──▶ your unified plugins
import { unified } from "unified";
import remarkMdi from "@illusions-lab/mdi-remark";
import remarkStringify from "remark-stringify";
const processor = unified().use(remarkMdi).use(remarkStringify);
const tree = processor.parse("{雪女|ゆき.おんな}が現れた。");
console.log(tree.children[0].children[0]);
// { type: "mdiRuby", base: "雪女", ruby: ["ゆき", "おんな"], data: {...} }

MDI’s own node types get an mdi-prefixed mdast type so they don’t collide with any existing mdast convention:

Rust IR typemdast type
rubymdiRuby (ruby field is unwrapped to a plain string or string array, dropping the {type, value} wrapper)
tcymdiTcy
breakmdiBreak
emmdiEm
noBreakmdiNoBreak
warichumdiWarichu
kernmdiKern
blankmdiBlank
pagebreakmdiPagebreak (a null variant is dropped entirely rather than kept as variant: null)
paragraph with indent/bottomordinary paragraph, with indent/bottom moved into data.mdiIndent/data.mdiBottommdast’s convention is that renderer-specific fields live under data, not as top-level node properties

Every other node (heading, list, link, text, GFM tables, footnotes, …) passes through unchanged, because it was already a standard mdast shape coming out of Rust.

resolveFrontmatter parses the raw YAML block into tree.data.frontmatter, with real defaults filled in — not just the raw string:

interface MdiFrontmatter {
mdi: string; // defaults to "2.0"
title?: string;
author?: string;
lang: string; // defaults to "ja"
date?: string;
writingMode: "horizontal" | "vertical"; // defaults to "horizontal"
pageProgression: "ltr" | "rtl"; // defaults to "rtl" when writingMode is "vertical", else "ltr"
}

Malformed YAML degrades to all-defaults rather than throwing — this adapter never fails a unified pipeline over front-matter syntax errors; Rust’s own mdi.version.unsupported diagnostic (see Diagnostics) is the one place a version problem is actually reported, and it isn’t surfaced through this adapter today.

Current implementation status: one-way today

Section titled “Current implementation status: one-way today”

Parsing (.mdi source → mdast) is fully Rust-backed and real. Serializing back (an edited mdast tree → canonical .mdi text) is also implemented, via mdiToMarkdown() — but it operates on the mdast shapes above directly; it is not yet wired through Rust’s own serialize_mdi normalization (see Rust Core API status), so an edited-and-restringified document is not guaranteed to apply MDI’s recommended-form normalization (e.g. automatically converting a 《《...》》 alias someone typed into `) the way serializeMdi()from@illusions-lab/mdi` does.

  • It is not required. @illusions-lab/mdi’s parse()/renderHtml() etc. work standalone; use this adapter only if you already have unified plugins that expect mdast.
  • It has no MDI grammar of its own — if remarkMdi and @illusions-lab/mdi’s parse() ever produced different trees for the same input, that would be a bug in the toMdast() mapping function, not a second parser to fix.
  • It does not normalize on stringify the way Rust’s serialize_mdi does — see above.