npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@isdk/mdast-plus

v0.3.5

Published

A semantic-first Markdown processing toolkit based on unified, remark, and rehype with a Fluent API and staged plugin system.

Readme

@isdk/mdast-plus

A "Semantic-First" Markdown processing toolkit based on unified, remark and rehype.

English | 简体中文 | GitHub

NPM version

@isdk/mdast-plus is a powerful extension to the unified ecosystem, designed to provide consistent, semantic-first normalization and transformation of Markdown content. It simplifies complex processing pipelines with a fluent API and a staged plugin system.

Features

  • Fluent API: Chainable interface mdast(input).use(plugin).toHTML().
  • Staged Plugins: Organize transformations into parse, normalize, compile, finalize, and stringify stages.
  • Semantic Normalization:
    • Directives: Canonicalizes admonition names and extracts titles from labels.
    • Table Spans: Support for rowspan and colspan in HTML output.
    • Code Meta: Structured parsing of code block metadata strings.
    • Image Sizing: URL "sugar" support (e.g., image.png#=500x300) for image dimensions.
    • Inline Styles: Built-in support for ==Highlight==, ~Subscript~, and ^Superscript^.
  • Deeply Typed: Built on TypeScript with full support for unist/mdast module augmentation.

Installation

npm install @isdk/mdast-plus
# or
pnpm add @isdk/mdast-plus

Basic Usage

HTML Conversion

import { mdast } from '@isdk/mdast-plus';

const html = await mdast(':::warning[Special Note]\nBe careful!\n:::')
  .toHTML();
// Result: <div title="Special Note" class="warning"><p>Be careful!</p></div>

Configure Input Options

You can pass options to input plugins (like remark-gfm or remark-parse) using the second argument of .from():

// Enable single tilde strikethrough (~text~)
const md = await mdast('Hello ~world~')
  .from('markdown', { remarkGfm: { singleTilde: true } })
  .toMarkdown();

Accessing Metadata (String Object)

You can request metadata (like that from html-readability) to be attached directly to the returned string object.

const result = await mdast(htmlInput)
  .useAt('parse', htmlReadabilityPlugins)
  .toMarkdown({ attachMetadata: true });

// result is a String object
console.log(result.toString()); // The Markdown content
console.log((result as any).title); // The extracted title
console.log((result as any).author); // The extracted author

Image Sizing

const html = await mdast('![Cat](cat.png#=500x300)').toHTML();
// Result: <img src="cat.png" alt="Cat" width="500" height="300">

AST Output

// Get the fully processed AST (after normalization)
const ast = await mdast('==Highlighted==').toAST();

// Get the raw AST (after parsing, before normalization)
const rawAst = await mdast('==Highlighted==').toAST({ stage: 'parse' });

Partial Execution & Debugging

You can stop the pipeline at any stage to inspect the intermediate AST, even when targeting a specific output format like html or markdown.

// Run 'markdown' pipeline but stop after 'parse' stage
// Returns the VFile with the AST at that point
const vfile = await mdast(input).to('markdown', { stage: 'parse' });
const ast = vfile.result; 

Runtime Overrides

You can override plugin options at the moment of execution using the overrides option in .to(). This is useful for adjusting behavior dynamically without rebuilding the pipeline.

await mdast(input)
  .use({ name: 'myPlugin', plugin: myPlugin, options: [{ foo: 'bar' }] }) // Default option
  .to('html', {
    overrides: {
      myPlugin: { foo: 'baz' } // Override for this run only
    }
  });

Advanced Pipeline

import { htmlReadabilityPlugins } from '@isdk/mdast-plus';

const vfile = await mdast(myInput)
  .data({ myGlobal: 'value' })
  // Add multiple plugins as an array at the 'compile' stage
  .use([pluginA, pluginB])
  // Or add a set of plugins at a specific stage with options
  .useAt('parse', htmlReadabilityPlugins, { 
    url: 'https://example.com/article',
    frontmatter: true, // Inject metadata as YAML frontmatter
    sourceLink: true     // Append source link at the bottom
  })
  .priority(10) // Run later than default plugins
  .to('markdown');

console.log(vfile.value); // The serialized Markdown with frontmatter

Plugin Behavior

mdast-plus uses unified internally. If you add the same plugin function multiple times, the last configuration overrides the previous ones.

Warning: Passing false as a plugin option (e.g., .use(myPlugin, false)) will disable the plugin entirely. For regular plugins, this means they simply won't run. For plugins marked as main: true (like replacements for the default parser), if they are disabled with false, they will not replace the default plugin of that stage, providing a safe fallback to the default behavior. If you want to bypass a plugin's logic while keeping it active (e.g. to maintain its parser), use an options object like { enable: false } instead.

// The plugin will run ONCE with option: 2
pipeline.use(myPlugin, { option: 1 });
pipeline.use(myPlugin, { option: 2 });

To run the same plugin logic multiple times (e.g., for different purposes), provide a distinct function reference:

// The plugin will run TWICE
pipeline.use(myPlugin, { option: 1 });
pipeline.use(myPlugin.bind({}), { option: 2 });

Arbitrary Formats

You can register custom input or output formats:

import { MdastPipeline, mdast, PipelineStage } from '@isdk/mdast-plus';

// Register a custom output format
MdastPipeline.register({
  id: 'reverse',
  output: [{
    plugin: function() {
      this.Compiler = (tree) => {
        // your custom stringification logic
        return '...';
      };
    },
    stage: PipelineStage.stringify
  }]
});

const result = await mdast('Hello').to('reverse');

Note: Format names are case-insensitive (always converted to lowercase internally).

Staged Processing

Plugins are executed based on their stage, order, and semantic constraints (before/after):

  1. parse (0): Input parsing (e.g., remark-parse).
  2. normalize (100): Cleanup and canonicalize the tree.
  3. compile (200): High-level semantic transformations.
  4. finalize (300): Final preparation before output (e.g. rehype-sanitize).
  5. stringify (400): Output generation.

Main Plugin Replacement

Each stage can have one "main" plugin. If a plugin is marked with main: true, it will replace the first plugin in that same stage. This is useful for swapping out default parsers or compilers while keeping the rest of the pipeline intact.

Note: Only one main plugin is allowed per stage. If multiple plugins are marked as main, only the last one defined will take effect as the replacement.

Core Plugins Included

| Plugin | Stage | Description | | :--- | :--- | :--- | | normalize-directive | normalize | Handles aliases (warn -> warning) and extracts titles. | | normalize-table-span | normalize | Migrates table cell spans to hProperties. | | extract-code-meta | normalize | Parses title="foo" from code block meta. | | image-size | normalize | Parses #=WxH from image URLs. | | normalize-inline-styles | normalize | Standardizes ==mark==, ~sub~, and ^sup^. | | html-readability | parse | Uses Mozilla's Readability to extract main content from HTML. Supports frontmatter injection, sourceLink (source link) footer, and smartExcerpt for intelligent summary management. Use htmlReadabilityPlugins array for easier setup. |

html-readability Options

  • url: (string) The URL of the HTML document.
  • frontmatter: (boolean | 'yaml' | 'toml') Whether to inject metadata as frontmatter. Default: false.
  • sourceLink: (boolean) Whether to append source link at the bottom. The link will be generated based on the original title/URL even if they are filtered or renamed in the fields option. Default: false.
  • fields: (string[] | object) Control which metadata fields are kept or how they are renamed.
    • If an array: acts as an allowlist (e.g., ['title', 'excerpt']).
    • If an object: maps original keys to new names (e.g., { title: 'headline' }). Only keys in the map are kept (Projection).
  • extraMetadata: (object) Extra key-value pairs to inject into the frontmatter. These will be merged with the readability metadata.
  • smartExcerpt: (boolean | object) Whether to remove the excerpt if it is a duplicate or near-duplicate of the main content. Default: true.
    • threshold: (number) The ratio of excerpt length to content length (0.0 to 1.0). Default: 0.6.
    • minContentLength: (number) Minimum length of the main content required to keep the excerpt. Default: 300.

Contributing

Please see CONTRIBUTING.md for guidelines on how to contribute to this project.

License

MIT