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

satteri

v0.6.3

Published

High-performance Markdown and MDX processing

Readme

satteri

Native-enhanced Markdown parsing and processing for JavaScript. Parse and compile in Rust, create flexible plugins in JavaScript.

Install

npm install satteri
yarn add satteri
pnpm add satteri

Usage

Markdown to HTML

import { markdownToHtml } from "satteri";

const html = markdownToHtml("# Hello\n\nWorld");
// <h1>Hello</h1>\n<p>World</p>

MDX to JS

import { mdxToJs } from "satteri";

const js = mdxToJs("# Hello\n\n<MyComponent />");

With plugins

Both functions accept mdastPlugins (operate on the Markdown AST before conversion to HAST) and hastPlugins (operate on the HAST before output).

import { markdownToHtml } from "satteri";
import { removeHeadings } from "./my-mdast-plugins.js";
import { addLinkClasses } from "./my-hast-plugins.js";

const html = markdownToHtml("# Hello\n\n[link](https://example.com)", {
  mdastPlugins: [removeHeadings],
  hastPlugins: [addLinkClasses],
});

If you're familiar with the unified ecosystem, mdast and hast plugins would be similar to remark and rehype plugins, respectively, and re-uses the same AST shape for both. This project does not currently have an equivalent of micromark or recma plugins.

Plugins

MDAST plugins

MDAST plugins run on the Markdown syntax tree, allowing you to do things like replace emoji shortcodes, unwrap images from paragraphs, or collect headings for a table of contents before Markdown is transformed to HTML / JS. Define visitor methods named after node types (heading, code, link, image, etc.). Each visitor receives the node and a context object for mutations.

const emojis = defineMdastPlugin({
  name: "emojis",
  text(node, ctx) {
    if (node.value.includes(":wave:")) {
      ctx.setProperty(node, "value", node.value.replaceAll(":wave:", "\u{1F44B}"));
    }
  },
});

Visitors can alternatively return a replacement node, raw Markdown, or raw HTML. This is useful when the replacement can't be expressed as property changes on the original node:

const highlightCode = defineMdastPlugin({
  name: "highlight-code",
  code(node) {
    return { rawHtml: `<pre class="highlighted">${escape(node.value)}</pre>` };
  },
});

All standard mdast node types are supported, plus GFM extensions (table, tableRow, tableCell, delete, footnoteDefinition, footnoteReference) and MDX nodes (mdxJsxFlowElement, mdxJsxTextElement, mdxFlowExpression, mdxTextExpression, mdxjsEsm) if enabled. For more information on the AST shape and node types, see the mdast spec.

HAST plugins

HAST plugins run on the HTML syntax tree after mdast-to-hast conversion, allowing you to do things like add classes to elements, set attributes on links, or wrap HTML elements with other elements, etc. Element visitors use a filter array to specify which tag names (or component names for MDX) to match.

const addLinkClasses = defineHastPlugin({
  name: "add-link-classes",
  element: {
    filter: ["a"],
    visit(node, ctx) {
      ctx.setProperty(node, "class", "link");
      ctx.setProperty(node, "target", "_blank");
    },
  },
});

Multiple filter groups on the same node type:

const multiFilter = defineHastPlugin({
  name: "multi-filter",
  element: [
    {
      filter: ["h1", "h2", "h3"],
      visit(node, ctx) {
        ctx.setProperty(node, "class", "heading");
      },
    },
    {
      filter: ["a"],
      visit(node, ctx) {
        ctx.setProperty(node, "target", "_blank");
      },
    },
  ],
});

An empty filter matches all elements, but can quickly become expensive when used on large documents, so use with caution:

const allElements = defineHastPlugin({
  name: "all-elements",
  element: {
    filter: [],
    visit(node, ctx) {
      ctx.setProperty(node, "data-visited", "true");
    },
  },
});

Non-element visitors (text, comment, raw, doctype, MDX expression types) use bare functions instead of filter objects:

const uppercaseText = defineHastPlugin({
  name: "uppercase-text",
  text(node, ctx) {
    ctx.setProperty(node, "value", node.value.toUpperCase());
  },
});

For more information on the AST shape and node types, see the hast spec.

Mutating nodes

Unlike remark and rehype plugins, nodes in Sätteri inside plugins are read-only. The AST lives in Rust memory and JavaScript only has a "view" over the different nodes, so direct mutations like node.value = "new text" have no effect. Use the context methods (ctx.setProperty, ctx.removeNode, ctx.replaceNode, etc.) instead, which send changes back to Rust in an efficient way.

// Won't work
heading(node, ctx) {
  node.depth = 2; // no effect, TypeScript will also complain that the node is readonly
}

// Do this instead
heading(node, ctx) {
  ctx.setProperty(node, "depth", 2);
}

// Or return a new node to replace it entirely, but this is less efficient and generally not recommended
heading(node) {
  return { ..node, depth: 2 };
}

Async plugins

Visitors can optionally be async. When any visitor is async, markdownToHtml and mdxToJs return a Promise<string> instead of string. For performance reasons, it is typically best to avoid async visitors, especially if your visitor matches a large number of nodes.

const highlighter = await createHighlighter({ themes: ["github-dark"], langs: ["js", "ts"] });

const asyncHighlight = defineMdastPlugin({
  name: "async-highlight",
  async code(node) {
    const html = await highlighter.codeToHtml(node.value, {
      lang: node.lang,
      theme: "github-dark",
    });
    return { rawHtml: html };
  },
});

// Returns Promise<string> when async plugins are used
const html = await markdownToHtml("```js\ncode\n```", {
  mdastPlugins: [asyncHighlight],
});

API

markdownToHtml(source: string, options?: CompileOptions)

Parse Markdown and compile to HTML. Returns string if all plugins are sync, Promise<string> if any are async.

const html = markdownToHtml("# Hello\n\nWorld");
// <h1>Hello</h1>\n<p>World</p>

mdxToJs(source: string, options?: MdxCompileOptions)

Parse MDX and compile to JavaScript module code. Same sync/async return behavior.

const js = mdxToJs("# Hello\n\n<MyComponent />");

Static optimization

The optimizeStatic option for MDX collapses static subtrees into pre-rendered HTML strings, reducing the number of JSX element calls in the output and increasing rendering performance. Dynamic content (JSX components, expressions) is preserved as normal JSX calls.

// Astro-style: wraps static HTML in <Fragment set:html="...">
const js = mdxToJs("# Hello\n\nWorld", {
  optimizeStatic: {
    component: "Fragment",
    prop: "set:html",
  },
});

// React-style: wraps in <div dangerouslySetInnerHTML={{ __html: "..." }}>
const js = mdxToJs("# Hello\n\nWorld", {
  optimizeStatic: {
    component: "div",
    prop: "dangerouslySetInnerHTML",
    wrapPropValue: true,
  },
});

The ignoreElements option can be used to exclude specific elements from collapsing.

Shoutout to Bjorn Lu for originally developing this optimization for Astro.

markdownToMdast(source: string)

Parse Markdown and return a complete mdast tree. This can be useful if you wanted to benefit from the fast native parsing of Sätteri, but ultimately wanted another pipeline to handle transformations and compilation, e.g. using remark plugins and remark-stringify to convert back to Markdown after processing.

import { markdownToMdast } from "satteri";

const tree = markdownToMdast("# Hello\n\nWorld");
// tree.children[0].type === "heading"
// tree.children[0].depth === 1

mdxToMdast(source: string)

Parse MDX and return a complete mdast tree.

const tree = mdxToMdast('<Component foo="bar" />');
// tree.children[0].type === "mdxJsxFlowElement"
// tree.children[0].name === "Component"

markdownToHast(source: string)

Parse Markdown, convert to hast, and return a complete hast tree.

const tree = markdownToHast("# Hello\n\nWorld");
// tree.children[0].type === "element"
// tree.children[0].tagName === "h1"

mdxToHast(source: string)

Parse MDX, convert to hast, and return a complete hast tree.

const tree = mdxToHast("<MyComponent />");
// tree.children[0].type === "mdxJsxFlowElement"
// tree.children[0].name === "MyComponent"

defineMdastPlugin(definition: MdastPluginDefinition)

Type-safe wrapper for MDAST plugin definitions.

defineHastPlugin(definition: HastPluginDefinition)

Type-safe wrapper for HAST plugin definitions.

CompileOptions

interface CompileOptions {
  mdastPlugins?: MdastPluginDefinition[];
  hastPlugins?: HastPluginDefinition[];
  filename?: string;
}

// mdxToJs accepts MdxCompileOptions, which extends CompileOptions
interface MdxCompileOptions extends CompileOptions {
  optimizeStatic?: OptimizeStaticConfig;
}

License

MIT