yume-dsl-markdown-it
v0.1.1
Published
markdown-it plugin that renders yume-dsl rich-text tags via token-walker
Downloads
15
Maintainers
Readme
English | 中文
yume-dsl-markdown-it
markdown-it plugin that renders
yume-dsl-rich-text tags inside Markdown.
Heads-up: The default tag prefix
$$conflicts with LaTeX math delimiters ($$...$$) used by most markdown-it math plugins. If your Markdown includes math, create the parser with a different prefix — e.g.createEasySyntax({ tagPrefix: "%%" })— to avoid collisions. See Custom Syntax.
The plugin is pure pipeline glue — it delegates tag grammar to the rich-text parser and rendering to
yume-dsl-token-walker.
No syntax rules are hard-coded; swap createEasySyntax({ tagPrefix: "%%" }) upstream and the plugin follows.
- Inline tags (
$$tag(...)$$) handled by an inline rule - Raw (
$$tag(arg)%...%end$$) and block ($$tag(arg)*...*end$$) tags handled by a block rule createTextoutput is automatically HTML-escaped viamd.utils.escapeHtml- Failed DSL fragments fall back to escaped source text by default (
onRenderFailure: "preserve") - Optional
shouldAttemptfast-path gate to skip parsing at non-DSL positions - Silent / non-silent result caching — each position is parsed at most once per parser state
- Block matching is bounded to the current markdown-it container, so blockquotes / lists do not leak prefixes or closing markers across boundaries
This package is in early development (v0.x). The API may change between minor versions. Once stable, breaking changes will land in major versions with explicit migration notes.
Table of Contents
Ecosystem
text ──▶ yume-dsl-rich-text (parse) ──▶ TextToken[] ──▶ yume-dsl-token-walker (interpret) ──▶ TNode[]
│ │
╰─────────── yume-dsl-markdown-it (glue) ───────────────╯
↓
markdown-it pipeline ──▶ HTML| Package | Role |
|------------------------------------------------------------------------------------|--------------------------------------------------------------|
| yume-dsl-rich-text | Parser — text to token tree |
| yume-dsl-token-walker | Interpreter — token tree to output nodes |
| yume-dsl-shiki-highlight | Syntax highlighting — tokens or TextMate grammar |
| yume-dsl-markdown-it | markdown-it plugin — DSL tags inside Markdown (this package) |
Install
npm install yume-dsl-markdown-it markdown-it
# or
pnpm add yume-dsl-markdown-it markdown-ityume-dsl-token-walker and yume-dsl-rich-text are dependencies and will be installed automatically.
markdown-it is a peer dependency — bring your own version (>=14).
Quick Start
import MarkdownIt from "markdown-it";
import {createParser, createSimpleInlineHandlers} from "yume-dsl-rich-text";
import type {InterpretRuleset} from "yume-dsl-token-walker";
import {yumePlugin} from "yume-dsl-markdown-it";
const parser = createParser({
handlers: createSimpleInlineHandlers(["bold", "italic"]),
});
const ruleset: InterpretRuleset<string> = {
createText: (text) => text,
interpret: (token, helpers) => {
if (token.type === "bold")
return {type: "nodes", nodes: ["<strong>", ...helpers.interpretChildren(token.value), "</strong>"]};
if (token.type === "italic")
return {type: "nodes", nodes: ["<em>", ...helpers.interpretChildren(token.value), "</em>"]};
return {type: "unhandled"};
},
onUnhandled: "flatten",
};
const md = new MarkdownIt().use(yumePlugin, {parser, ruleset, env: undefined});
md.render("# Hello $$bold(world)$$");
// → <h1>Hello <strong>world</strong></h1>Options
interface YumePluginOptions<TEnv = undefined> {
/** yume-dsl-rich-text parser instance */
parser: Parser;
/** token-walker ruleset whose string nodes represent HTML fragments */
ruleset: InterpretRuleset<string, TEnv>;
/** environment value forwarded to every interpret call */
env: TEnv;
/** optional fast-path gate before delegating to the parser */
shouldAttempt?: (src: string, pos: number) => boolean;
/** render contract for parse / interpret failures after a DSL match is confirmed */
onRenderFailure?: "preserve" | "throw" | ((context: RenderFailureContext<TEnv>) => string);
}shouldAttempt
Called at every character position before the parser runs. Return false to skip parsing entirely at
that position. Useful when you know your DSL always starts with a fixed prefix:
md.use(yumePlugin, {
parser,
ruleset,
env: undefined,
shouldAttempt: (src, pos) => src.charCodeAt(pos) === 0x24 && src.charCodeAt(pos + 1) === 0x24,
});onRenderFailure
Controls what happens when a DSL tag is structurally matched but interpretTokens throws:
| Value | Behavior |
|--------------|--------------------------------------------------------------|
| "preserve" | Emit the original source text, HTML-escaped |
| "throw" | Re-throw the error |
| function | Call with { error, source, env, form }, return HTML string |
Default: "preserve".
Tag Forms
The plugin recognizes all three yume-dsl tag forms:
| Form | Syntax | Rule | Example |
|--------|------------------------------|--------|-----------------------------------|
| Inline | $$tag(content)$$ | Inline | $$bold(hello)$$ |
| Raw | $$tag(arg)% content %end$$ | Block | $$code(ts)% const x = 1; %end$$ |
| Block | $$tag(arg)* content *end$$ | Block | $$collapse(note)* ... *end$$ |
Inline tags live inside paragraphs, headings, list items, etc. Raw and block tags are block-level — they stand alone between paragraphs. When used inside blockquotes or list items, block/raw matching stays inside that container.
Custom Syntax
The plugin does not hard-code any delimiter. If you create a parser with custom syntax, the plugin follows automatically:
import {createEasySyntax, createParser, createSimpleInlineHandlers} from "yume-dsl-rich-text";
const parser = createParser({
syntax: createEasySyntax({tagPrefix: "%%"}),
handlers: createSimpleInlineHandlers(["bold"]),
});
const md = new MarkdownIt().use(yumePlugin, {parser, ruleset, env: undefined});
md.render("%%bold(hello)%%");
// → <p><strong>hello</strong></p>For full syntax customization details, see the
yume-dsl-rich-text documentation.
Error Handling
- Structural match but render failure: controlled by
onRenderFailure(default:"preserve") - No structural match: the text passes through to markdown-it as-is — no error
- Parser exception during structural scan: silently skipped (returns
falseto markdown-it) - Inline matches inside link labels / other silent scans: consume input correctly and do not break markdown-it
The plugin never throws by default. Set onRenderFailure: "throw" to surface errors during development.
Safety
createTextoutput is wrapped withmd.utils.escapeHtml— plain text nodes are always escaped- Failed fragments are escaped before emitting when
onRenderFailureis"preserve" - The plugin does not inject raw HTML from user input unless your
ruleset.interpretexplicitly returns it ruleset.createTextshould return un-escaped plain text; the plugin handles escaping
Changelog
See CHANGELOG.md.
License
MIT © 星野夢華
