mdast-util-compact

Merges adjacent text nodes and blockquotes in an mdast syntax tree to keep it compact after edits.

Library
npm
v5.0.0
7stars
MIT License

Repository Health

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

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
65/100Good
Architecture65
Code Quality90
Innovation75
Learning Curve30

mdast-util-compact is a small, focused utility for the unified/syntax-tree ecosystem that makes an mdast (Markdown Abstract Syntax Tree) compact by merging adjacent text nodes and adjacent blockquotes into single nodes, mirroring how a fresh parse of the same content would be structured. It exists because programmatic edits to an mdast tree — inserting, removing, or splitting nodes — commonly leave behind fragmented siblings (e.g. two text nodes side by side, or two blockquote nodes back to back) that a parser would never produce on its own.

The package exposes a single function, compact(tree), which walks the tree with unist-util-visit and, for each text or blockquote node with a preceding sibling of the same type, concatenates their values (or children, for blockquotes) and merges their position information, mutating the tree in place. It is intentionally narrow in scope: the README itself notes it should rarely be needed if trees are kept clean during construction, positioning it as a defensive normalization step for tooling that manipulates markdown ASTs — remark plugins, custom serializers, or any code that reshapes mdast trees before stringifying them back to markdown.

What You Get

  • A single compact(tree) function that mutates an mdast tree in place, no configuration or options object required
  • Automatic merging of adjacent text nodes, concatenating their string values
  • Automatic merging of adjacent blockquote nodes, concatenating their children arrays
  • Position (position.start/position.end) reconciliation across merged nodes so source-map data stays accurate
  • Full TypeScript types generated from JSDoc, with 100% type-coverage enforced in CI
  • Zero runtime configuration and zero side effects (sideEffects: false), safe to drop into any mdast-processing pipeline

Common Use Cases

  • Normalizing an mdast tree after a remark/unified plugin inserts or removes nodes, so subsequent plugins or serializers see a clean tree
  • Cleaning up trees built by hand or by test fixtures with unist-builder, where adjacent text/blockquote nodes are easy to introduce accidentally
  • Post-processing step before stringifying an mdast tree back to Markdown, avoiding redundant sibling nodes in the output
  • Sanitizing ASTs produced by custom parsers or transformers that don’t guarantee text-node coalescing on their own

Under The Hood

Architecture The entire package is one exported function, compact(tree), defined in lib/index.js and re-exported from the package root index.js. It delegates tree traversal to unist-util-visit, supplying a visitor callback that inspects each node’s parent, index, and type; when a text or blockquote node’s immediate previous sibling shares its type, the callback merges value or children onto the previous node, splices the current node out of parent.children, reconciles position.end, and returns the previous index so the visitor re-checks the same position after the splice. There is no internal module boundary to speak of beyond this one file — the whole implementation is a single dense function, appropriate for a utility this narrow in scope.

Tech Stack The package is pure ESM ("type": "module", exports: "./index.js") targeting Node.js 16+, written in plain JavaScript with JSDoc-based typing compiled to a .d.ts declaration via tsc --build. Its only runtime dependencies are unist-util-visit for tree traversal, devlop for development-time assertions, and @types/mdast for its Nodes type import. Tooling is entirely devDependency-based: xo (ESLint preset) plus prettier for formatting, remark-cli with remark-preset-wooorm to lint its own README, type-coverage to enforce 100% typed code, and c8 for coverage — all wired into npm test (buildformattest-coverage) and run in a GitHub Actions workflow against a Node version matrix with Codecov upload.

Code Quality Tests live in a single test.js using Node’s built-in node:test and node:assert/strict, covering the public API surface, basic text-merging, position-merging, merging across texts with incompatible positions, and blockquote merging — four focused test cases exercising the one exported function. Coverage is enforced via c8 --100, meaning the test suite must hit full statement/branch coverage for the package to pass CI, and type-coverage similarly requires 100% typed code with strict: true. Error handling is minimal by design (devlop’s assert is used only as an internal invariant check, not for user-facing validation), which is consistent with a small transform utility rather than a public-facing API needing defensive input handling.

API Design The package exposes exactly one named export, compact, with a single required parameter and no options object, no default export, and no configuration surface at all — about as low-boilerplate as a utility can get. Usage requires no setup beyond importing the function and calling it on a tree, and the README documents the one function directly under an ## API heading with parameter and return-value sections. This minimal-surface-area design trades flexibility for simplicity, matching the library’s stated philosophy that it should rarely be needed and, when it is, should require no thought beyond calling one function.

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