mdast-util-mdx

Mdast extensions that parse and serialize MDX syntax — ESM, JSX, and expressions — inside markdown syntax trees.

Library
npm
v3.0.0
25stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
26/100Needs Attention
Development Activity0
Maintenance20
Community12
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
79/100Good
Architecture78
Code Quality90
Innovation92
Learning Curve55

mdast-util-mdx is the combined mdast extension for working with MDX syntax — ESM import/export statements, JSX elements, and {} expressions — inside a markdown abstract syntax tree. It bundles three sibling packages (mdast-util-mdx-expression, mdast-util-mdx-jsx, and mdast-util-mdxjs-esm) behind two functions, mdxFromMarkdown() and mdxToMarkdown(options?), so tooling built on mdast-util-from-markdown and mdast-util-to-markdown can parse MDX documents into a syntax tree and serialize them back to text without wiring up each extension individually.

It is a low-level building block maintained by the syntax-tree/unified collective, used internally by remark-mdx and, transitively, by the MDX compiler itself. Most application code reaches it indirectly through remark-mdx or an MDX-aware pipeline; you reach for this package directly only when you’re building custom markdown/MDX tooling — linters, formatters, codemods, or static-analysis tools — that needs direct access to the mdast syntax tree rather than a full compile-to-JSX pipeline.

What You Get

  • mdxFromMarkdown() — a ready-to-use array of mdast-util-from-markdown extensions covering ESM, JSX, and expressions in one call
  • mdxToMarkdown(options?) — a single mdast-util-to-markdown extension that serializes those same node types back to MDX text
  • New mdast node types (mdxjsEsm, mdxJsxFlowElement, mdxJsxTextElement, mdxFlowExpression, mdxTextExpression) registered against @types/mdast for full TypeScript coverage
  • Serialization options (quote, quoteSmart, tightSelfClosing, printWidth) for controlling how JSX attributes are printed back to markdown
  • ESTree Program nodes attached under data.estree on ESM and expression nodes when combined with micromark-extension-mdxjs’s addResult option

Common Use Cases

  • Building a custom remark/mdast plugin that needs to inspect or transform MDX-specific nodes (JSX, imports, expressions) in a syntax tree
  • Writing a linter or formatter for .mdx files that operates on the parsed tree rather than raw text
  • Implementing an alternative or extended MDX compiler that reuses the standard mdast node shapes instead of inventing new ones
  • Round-tripping MDX documents — parse to a tree, programmatically edit nodes, and serialize back to valid MDX — for codemods or content migrations

Under The Hood

Architecture The package is a thin facade: index.js re-exports two functions from a single ~50-line lib/index.js, which itself does no parsing or serialization of its own. mdxFromMarkdown() returns an array combining the mdast-util-from-markdown extensions of three sibling syntax-tree packages (mdast-util-mdx-expression, mdast-util-mdx-jsx, mdast-util-mdxjs-esm); mdxToMarkdown(options) returns one aggregated mdast-util-to-markdown extension, forwarding its options argument only to the JSX extension since that’s the only one with serialization knobs. There is no internal state, no classes, and no business logic beyond composition — if the shared extension contract from mdast-util-from-markdown/mdast-util-to-markdown changes, this package breaks immediately since it holds nothing of its own to buffer that change.

Tech Stack A pure ESM-only Node.js/browser library ("type": "module", single ./index.js export, consumable via esm.sh in Deno and browsers). Types are authored as hand-written .d.ts re-exports layered over JSDoc-typed source, compiled with tsc --build in declaration-only mode under a strict tsconfig.json (strict, exactOptionalPropertyTypes, node16 module resolution) and checked with type-coverage for full type coverage. All five runtime dependencies are sibling mdast-util-* packages from the same org; there is no bundler, no framework, and no external service integration — it’s a dependency-thin composition layer.

Code Quality Tests run entirely on Node’s built-in node:test and node:assert/strict (no external test framework), asserting deep equality against full ESTree-plus-mdast JSON trees for both parsing and serialization paths; c8 enforces 100% coverage as part of npm test, and type-coverage separately enforces 100% type coverage as part of the build. Linting and formatting are unified through xo (an opinionated ESLint preset with Prettier integration) plus remark-preset-wooorm for consistent README/doc formatting, all wired into GitHub Actions CI across two Node versions. There’s little explicit error handling in the source itself, which is consistent with its role as a stateless composition layer rather than a gap in defensive coding.

API Design The public surface is exactly two functions, named and shaped identically to every other mdast-util-*-from-markdown/-to-markdown extension in the ecosystem, so anyone who has used one sibling package already knows this one. Getting started requires no configuration for the common case — pass the from-markdown extensions and the to-markdown extension straight into fromMarkdown/toMarkdown alongside a micromark MDX syntax extension — with a single optional options object exposed only for advanced JSX serialization control. The package also re-exports every new node’s TypeScript type so consumers get tree-node autocomplete simply by referencing the package once in their types, exactly as the README documents.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search