MDAST Heading Range Utility
mdast-util-heading-range is a utility for working with Markdown Abstract Syntax Trees (MDAST) to identify and manipulate content sections defined by headings. It allows developers to find a specific heading, capture all content nodes within its section (up to the next heading of the same or lower depth, or the end of the document), and then apply a handler function to modify or replace that content. The current stable version is 4.0.0, released in a mostly ad-hoc fashion following the syntax-tree ecosystem's release schedule, typically with minor versions for features and patch versions for fixes. Key differentiators include its focus on heading-based content segmentation, offering a programmatic way to update generated sections (like a Table of Contents), and its integration within the broader `unified` and `remark` ecosystem. It is an ESM-only package since version 3.0.0 and requires Node.js 16 or higher as of version 4.0.0.
Common errors
-
ERR_REQUIRE_ESM
cause Attempting to `require()` an ESM-only package.fixChange `const { headingRange } = require('mdast-util-heading-range')` to `import { headingRange } from 'mdast-util-heading-range'` and ensure your project is configured for ES modules (e.g., `"type": "module"` in `package.json`). -
TypeError: Cannot read properties of undefined (reading 'type')
cause Passing an improperly typed MDAST tree or `Node` to `headingRange` or its handler, especially after recent type updates.fixEnsure the `tree` argument is a valid `mdast.Root` node and that any nodes returned by the handler conform to `mdast.Node` types. Review type definitions, especially after `v4.0.0`'s `@types/mdast` update. -
Error: `test` must be a string, array of strings, regular expression, function, or `options` object.
cause The `test` argument for `headingRange` is not in a valid format.fixProvide a string for the heading text, an array of strings, a regular expression, a test function, or an options object with a `test` property. Example: `headingRange(tree, 'My Heading', handler)` or `headingRange(tree, { test: 'My Heading' }, handler)`.
Warnings
- breaking Version 4.0.0 requires Node.js 16 or higher. Older Node.js versions are no longer supported.
- breaking The package now uses `export` maps, which affects how it's imported in some environments, particularly those relying on internal or non-standard paths.
- breaking The `ZoneInfo` TypeScript type was removed and renamed to `Info` in version 4.0.0.
- breaking Version 3.0.0 transitioned to an ESM-only package. CommonJS `require()` is no longer supported.
- gotcha When `ignoreFinalDefinitions: true` is used, the handler will exclude final definition nodes found within a section.
Install
-
npm install mdast-util-heading-range -
yarn add mdast-util-heading-range -
pnpm add mdast-util-heading-range
Imports
- headingRange
const headingRange = require('mdast-util-heading-range')import { headingRange } from 'mdast-util-heading-range' - Info
import type { Info } from 'mdast-util-heading-range' - Handler
import type { Handler } from 'mdast-util-heading-range'
Quickstart
import {read} from 'to-vfile';
import {remark} from 'remark';
import {headingRange} from 'mdast-util-heading-range';
const markdownContent = `# Foo\n\nBar.\n\n# Baz\n\nOther content.\n\n## Sub Heading\n\nSub content.`;
async function processMarkdown() {
const file = await remark()
.use(myPluginThatReplacesFoo)
.process(String(markdownContent));
console.log(String(file));
}
/** @type {import('unified').Plugin<[], import('mdast').Root>} */
function myPluginThatReplacesFoo() {
return function (tree) {
headingRange(tree, 'Foo', function (start, nodes, end) {
// This handler receives the 'Foo' heading (start),
// nodes between 'Foo' and 'Baz' (nodes), and the 'Baz' heading (end).
// It replaces the content of the 'Foo' section.
return [
start,
{type: 'paragraph', children: [{type: 'text', value: 'Content updated by mdast-util-heading-range.'}]},
{type: 'list', children: [{type: 'listItem', children: [{type: 'paragraph', children: [{type: 'text', value: 'New list item.'}]}]}]},
end
]
})
}
}
processMarkdown();