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

@f-o-t/markdown

v1.0.2

Published

CommonMark compliant markdown parser with AST generation and streaming support

Readme

@f-o-t/markdown

A CommonMark compliant markdown parser with AST generation, streaming support, and bidirectional conversion (Markdown ↔ HTML).

Installation

# npm
npm install @f-o-t/markdown

# pnpm
pnpm add @f-o-t/markdown

# bun
bun add @f-o-t/markdown

Features

  • CommonMark compliant - Full support for the CommonMark specification
  • GFM extensions - Tables, task lists, strikethrough
  • Bidirectional conversion - Parse markdown to AST, generate markdown from AST
  • HTML rendering - Convert markdown AST to HTML with customization options
  • HTML to Markdown - Convert HTML content back to markdown
  • Streaming support - Process large files efficiently with streaming APIs
  • Batch processing - Parse multiple files with progress events
  • Type-safe - Full TypeScript support with Zod schemas for runtime validation
  • Zero dependencies (except Zod for schema validation)

Quick Start

Parsing Markdown

import { parse, parseOrThrow } from "@f-o-t/markdown";

// Safe parsing with result type
const result = parse("# Hello **World**");
if (result.success) {
  console.log(result.data.root.children);
}

// Throws on error
const doc = parseOrThrow("# Hello **World**");
console.log(doc.root.children);

Generating Markdown

import { generate, generateHeadingString, generateLinkString } from "@f-o-t/markdown";

// Generate from AST
const markdown = generate(doc);

// Helper functions for common elements
generateHeadingString(1, "Title");           // "# Title"
generateLinkString("Example", "https://example.com"); // "[Example](https://example.com)"

HTML Rendering

import { parse, renderToHtml } from "@f-o-t/markdown";

const doc = parse("# Hello **World**");
const html = renderToHtml(doc.data);
// => "<h1 id="hello-world">Hello <strong>World</strong></h1>"

HTML to Markdown

import { htmlToMarkdown } from "@f-o-t/markdown";

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

API Reference

Parsing

| Function | Description | |----------|-------------| | parse(content, options?) | Parses markdown, returns { success, data } or { success, error } | | parseOrThrow(content, options?) | Parses markdown, throws on error | | parseToAst(content, options?) | Returns just the AST root node | | parseBuffer(buffer, options?) | Parses from Uint8Array with encoding detection | | parseBufferOrThrow(buffer, options?) | Same as above, throws on error | | isValidMarkdown(content) | Returns true if content can be parsed |

Parse Options

interface ParseOptions {
  positions?: boolean;      // Include position info (default: true)
  preserveSource?: boolean; // Keep original source (default: false)
}

Utility Functions

import {
  extractText,
  countWords,
  getHeadings,
  getLinks,
  getImages,
  getCodeBlocks,
} from "@f-o-t/markdown";

extractText("**Hello** *world*");      // "Hello world"
countWords("Hello **world**!");        // 2
getHeadings("# Title\n## Section");    // [{ level: 1, text: "Title" }, ...]
getLinks("[Example](url)");            // [{ text: "Example", url: "url" }]
getImages("![Alt](image.png)");        // [{ alt: "Alt", url: "image.png" }]
getCodeBlocks("```js\ncode\n```");     // [{ lang: "js", code: "code" }]

Generation

| Function | Description | |----------|-------------| | generate(document, options?) | Generates markdown from AST | | generateNode(node, options?) | Generates markdown from any node | | createGenerator(options?) | Creates incremental generator |

Generate Options

interface GenerateOptions {
  lineEnding?: "\n" | "\r\n";   // Default: "\n"
  indent?: number;               // List indent (default: 3)
  setext?: boolean;              // Setext headings (default: false)
  fence?: "`" | "~";             // Code fence char (default: "`")
  fenceLength?: number;          // Fence length (default: 3)
  emphasis?: "*" | "_";          // Emphasis marker (default: "*")
  strong?: "**" | "__";          // Strong marker (default: "**")
  bullet?: "-" | "*" | "+";      // List bullet (default: "-")
}

String Helpers

import {
  generateHeadingString,
  generateLinkString,
  generateImageString,
  generateCodeBlockString,
  generateListString,
  generateBlockquoteString,
  generateEmphasisString,
  generateStrongString,
  generateInlineCodeString,
  generateTableString,
  generateTaskListString,
  generateStrikethroughString,
} from "@f-o-t/markdown";

// Examples
generateHeadingString(2, "Section");              // "## Section"
generateCodeBlockString("const x = 1;", "js");    // "```js\nconst x = 1;\n```"
generateListString(["A", "B"], true);             // "1. A\n2. B"
generateTableString(["Name", "Age"], [["Alice", "30"]]);
generateTaskListString([{ text: "Done", checked: true }]);

HTML Rendering

import { renderToHtml, renderNodeToHtml } from "@f-o-t/markdown";

const html = renderToHtml(document, {
  sanitizeHtml: true,           // Escape raw HTML (default: true)
  externalLinksNewTab: false,   // Add target="_blank" (default: false)
  classPrefix: "",              // CSS class prefix
  softBreakAsBr: false,         // Render soft breaks as <br>
  transformUrl: (url, type) => url, // Custom URL transformer
  elementAttributes: {          // Custom attributes per element
    link: { rel: "nofollow" },
    image: { loading: "lazy" },
  },
});

HTML to Markdown

import { htmlToMarkdown, parseHtml, htmlAstToMarkdownAst } from "@f-o-t/markdown";

// Direct conversion
const markdown = htmlToMarkdown("<h1>Hello</h1>");

// Or step by step
const htmlAst = parseHtml("<h1>Hello</h1>");
const markdownAst = htmlAstToMarkdownAst(htmlAst);

Streaming

import {
  parseStream,
  parseStreamToDocument,
  parseBatchStream,
} from "@f-o-t/markdown";

// Stream from fetch
const response = await fetch("large-doc.md");
for await (const event of parseStream(response.body)) {
  if (event.type === "block") {
    console.log("Parsed block:", event.data.type);
  } else if (event.type === "complete") {
    console.log("Done!");
  }
}

// Collect to document
const doc = await parseStreamToDocument(response.body);

// Batch processing
const files = [
  { filename: "a.md", content: "# A" },
  { filename: "b.md", content: "# B" },
];
for await (const event of parseBatchStream(files)) {
  switch (event.type) {
    case "file_start":
      console.log(`Processing ${event.filename}`);
      break;
    case "file_complete":
      console.log(`Completed ${event.filename}`);
      break;
    case "batch_complete":
      console.log(`Done: ${event.totalFiles} files`);
      break;
  }
}

Stream Options

interface StreamOptions {
  positions?: boolean;       // Include positions (default: true)
  maxBufferSize?: number;    // Max buffer size in bytes (default: 10MB)
  chunkSize?: number;        // Chunk size for batch (default: 64KB)
}

AST Types

All node types are available as TypeScript types and Zod schemas:

import type {
  MarkdownDocument,
  DocumentNode,
  BlockNode,
  InlineNode,
  HeadingNode,
  ParagraphNode,
  CodeBlockNode,
  ListNode,
  TableNode,
  // ... and more
} from "@f-o-t/markdown";

import {
  documentNodeSchema,
  headingNodeSchema,
  paragraphNodeSchema,
  // ... Zod schemas for validation
} from "@f-o-t/markdown";

Utilities

import {
  normalizeLineEndings,
  normalizeEscapedNewlines,
  normalizeMarkdownEmphasis,
} from "@f-o-t/markdown";

// Normalize to \n
normalizeLineEndings("hello\r\nworld"); // "hello\nworld"

// Convert escaped newlines to actual newlines
normalizeEscapedNewlines("hello\\nworld"); // "hello\nworld"

CommonMark Compliance

This library implements the CommonMark specification with additional support for GFM (GitHub Flavored Markdown) extensions:

  • Block elements: Headings, paragraphs, code blocks (fenced and indented), blockquotes, lists (ordered, unordered, task lists), thematic breaks, HTML blocks, tables
  • Inline elements: Emphasis, strong emphasis, links, images, code spans, hard/soft breaks, HTML inline, strikethrough

License

MIT