mdast-util-compact
Merges adjacent text nodes and blockquotes in an mdast syntax tree to keep it compact after edits.
Repository Health
Technical Analysis
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
textnodes, concatenating their string values - Automatic merging of adjacent
blockquotenodes, 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 (build → format → test-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.