Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

myst-parser-unified

A proof of concept for parsing MyST Markdown with micromark, the parser behind remark and unified.

mystmd parses MyST with markdown-it, which 🟢 records line numbers but not columns. markdown-it also 🟢 doesn’t fit the unified ecosystem. This repo tests how hard it would be to parse MyST with micromark instead.

To measure that, we parse the MyST in each myst-spec example with our parser and with mystmd’s, and compare the two mdast trees. The passing examples are in scoreboard.json.

Score: 65 of 90 MyST examples match mystmd’s tree straight from the parser, from npm run score. Score per file has the other scores, and What we learned has a comparison on real MyST projects.

What we found

Every MyST syntax the spec uses, such as roles, directives, targets, comments and block breaks, works as a micromark extension, with exact positions. No example fails because of how MyST syntax is parsed. They fail because mystmd’s tree isn’t plain mdast: it uses curly quotes, sets list spread differently, turns bare URLs into links, nests content in block nodes, and so on. toMystmd fixes most of those, but not curly quotes or bare URLs. The differences page lists them. myst-spec also includes the CommonMark spec examples, scored separately, and those mostly measure where markdown-it and micromark differ.

Use it

It isn’t on npm, so install it with npm install github:choldgraf/myst-parser-unified. npm install runs the build for you.

parse(src) returns an mdast tree, and the remarkMyst plugin adds the same MyST syntax to remark. toMystmd(tree) changes a tree, in place, into mystmd’s shape:

import { parse, remarkMyst, toMystmd } from 'myst-parser-unified';
import remarkParse from 'remark-parse';
import { unified } from 'unified';

const tree = parse(src);
const sameTree = unified().use(remarkParse).use(remarkMyst).parse(src);
const mystmdTree = toMystmd(parse(src));

mystParse from myst-parser-unified/mystmd is a drop-in for myst-parser’s mystParse. It takes the same directives, roles, vfile and markdownit options, returns the same tree, and writes the same messages to vfile:

import { mystParse } from 'myst-parser-unified/mystmd';

const tree = mystParse(src, { directives, roles, vfile, markdownit: { linkify: true } });

It runs mystmd’s own directives and roles, so it needs the myst-parser version pinned in package.json as a peer dependency, and it only runs in Node. The main entry doesn’t need myst-parser. The differences page says what it doesn’t do the same.

micromark isn’t incremental, so this isn’t meant for highlighting an editor on every keystroke. It can’t turn a tree back into MyST yet: there are no mdast-util-to-markdown extensions, so remark-stringify doesn’t know the MyST nodes.

Run it

You need Node 24 or later, which runs the TypeScript files directly.

Score per file

Each row is a myst-spec example file, and the last three rows are the MyST, CommonMark and combined totals. Each column counts the examples that match:

The numbers come from the last npm run score.

TitleRawAdaptedTransformedDropinCeilingTotal
blocks044404
cmark_spec_0.30398625420625558651
comments330303
commonmark.breaks222222
commonmark.code131333
commonmark.headings222222
commonmark.html111111
commonmark.links233323
commonmark.lists020202
commonmark.paragraphs333333
commonmark.quotes111111
directives.admonitions.simple101010101010
directives.admonitions442424
directives.code441515
directives.figure000505
directives.generic222222
directives.image233323
directives.math222222
directives.table020212
footnotes001001
references.equations332323
references.figures993939
references.headings444444
references.tables060606
references.target111111
roles.generic111111
roles.html.abbr333333
roles.html444444
roles.math111111
MyST658357895390
CommonMark398625420625558651
TOTAL463708477714611741