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

@lacspace/markdown

v1.1.1

Published

A small, safe Markdown → HTML renderer — headings with anchor ids, nested & task lists, fenced code, blockquotes, GFM tables, strikethrough, images and links. HTML in the source is escaped. Also parses YAML-ish frontmatter, builds a nested table of conten

Readme

@lacspace/markdown

Markdown → HTML in a few KB — safe by default, with a table-of-contents extractor.

npm version minzipped types license

1.1.1 — security: two sanitizeHtml bypasses closed

Both let a browser execute what the sanitizer had approved. If you sanitize untrusted HTML with this package, update.

  • Entity-encoded schemes. The scheme check ran on the raw attribute, so href="javascript:alert(1)" passed — its first character is &, which is not a scheme — yet the browser decodes the reference before parsing the URL and executes javascript:. Decimal, hex (with or without the ;), : and 	 all reached the same place. The check now runs on the decoded value, one pass, which is also what the browser does: a double-encoded j decodes to literal text and is correctly left alone.
  • Unbalanced quotes. <img src="x onerror=alert(1)> cannot match a well-formed tag, so it fell through the sanitizer untouched, and a browser's forgiving parser ran the handler. Anything tag-shaped that is not a well-formed tag is now escaped to text. A well-formed tag whose attribute contains > (title="a>b") still round-trips intact.

Everything a blog or docs page needs — headings with anchor ids, nested & task lists, fenced code, blockquotes, GFM tables, images and links — rendering to clean HTML with source HTML escaped by default. No dependencies, isomorphic.

  • ✍️ Headings (anchor ids), nested & task lists, fenced code, blockquotes, GFM tables, strikethrough
  • 🔒 Safe by default — raw HTML in the source is escaped; javascript: / data: URLs neutralized
  • 🧭 extractHeadings() / tableOfContents() build a (nested) TOC; slugify() / slugifyHeading() for anchor links
  • 📝 parseFrontmatter() splits a --- YAML-ish block into { data, content }
  • ✂️ toPlainText() / excerpt() for meta descriptions & previews
  • 🧼 sanitizeHtml() to clean untrusted HTML from elsewhere
  • 🎛️ Options — headingIds, headingOffset, openLinksInNewTab, autolinkBareUrls
  • ⚡ Zero dependencies · 🌍 isomorphic · 📦 ESM + CJS · fully typed

New in 1.1.0 — parseFrontmatter, tableOfContents + slugifyHeading, toPlainText + excerpt, sanitizeHtml, and an opt-in autolinkBareUrls option for GFM bare-URL autolinking. All additive — existing output and every existing export are unchanged.

Install

npm i @lacspace/markdown

Use it

import { markdownToHtml } from "@lacspace/markdown";

markdownToHtml("# Hello\n\nSome **bold** text & a <script>alert(1)</script>.");
// <h1 id="hello">Hello</h1>
// <p>Some <strong>bold</strong> text &amp; a &lt;script&gt;alert(1)&lt;/script&gt;.</p>
//   ↑ headings get anchor ids; raw HTML is escaped, never executed

const html = markdownToHtml(`
# Getting started

Some **bold** text, a [link](https://lacspace.com) and \`inline code\`.

- a list
  - that nests
- [x] and task items

| Feature | Status |
| ------- | :----: |
| Tables  |   ✅   |

\`\`\`ts
const x = 1;
\`\`\`
`);

A table of contents, for free

extractHeadings(md);
// flat: [{ level: 1, text: "Getting started", id: "getting-started" }, …]
slugify("Hello, World!"); // "hello-world"

tableOfContents(md);
// nested: [{ level: 1, text: "Getting started", slug: "getting-started", children: [ … ] }]
slugifyHeading("Café déjà vu"); // "café-déjà-vu" (unicode-aware, GitHub-style)

Frontmatter, plain text & excerpts

parseFrontmatter("---\ntitle: Hello\ntags: [a, b]\n---\n# Body");
// { data: { title: "Hello", tags: ["a", "b"] }, content: "# Body" }

toPlainText("# Hi\n\nSome **bold** [text](/x).");   // "Hi\nSome bold text."
excerpt(longMarkdown, { length: 160 });             // word-boundary preview + "…"

Cleaning untrusted HTML

markdownToHtml is already safe (it escapes source HTML). sanitizeHtml is for HTML that came from somewhere else (a paste, a CMS field) that you want to display:

sanitizeHtml('<p>ok</p><script>alert(1)</script><a href="javascript:x" onclick="y">z</a>');
// '<p>ok</p><a href="#">z</a>'

GFM bare-URL autolinking (opt-in)

markdownToHtml("see https://lacspace.com");                          // …see https://lacspace.com (plain)
markdownToHtml("see https://lacspace.com", { autolinkBareUrls: true }); // …<a href="https://lacspace.com">…</a>

Supports

  • Headings #–###### with auto anchor ids (toggle with headingIds, shift with headingOffset)
  • Lists — ordered, unordered, nested, and task lists (- [x])
  • Fenced code ``` with language-* class (HTML-escaped)
  • Blockquotes (nested Markdown inside)
  • GFM tables with column alignment
  • Inline — **bold**, *italic*, ~~strike~~, `code`, [links](…), ![images](…), <autolinks>, hard line breaks
  • Safety — raw HTML in the source is escaped, not passed through

Options

| Option | Default | Meaning | | --- | --- | --- | | headingIds | true | add slug ids to headings | | headingOffset | 0 | shift levels (1 → # becomes <h2>) | | openLinksInNewTab | false | add target="_blank" rel="noopener noreferrer" to external links | | autolinkBareUrls | false | GFM-style autolink bare https://… / www.… URLs (off by default so output is unchanged) |

API

| Export | Signature | Purpose | | --- | --- | --- | | markdownToHtml | (md, options?) => string | render Markdown to HTML | | extractHeadings | (md) => { level, text, id }[] | flat heading outline | | slugify | (text) => string | anchor slug (ASCII) | | slugifyHeading | (text) => string | GitHub-style slug (unicode-aware) | | tableOfContents | (src, { minLevel?, maxLevel? }?) => TocNode[] | nested { level, text, slug, children } TOC (unique slugs) | | parseFrontmatter | (src) => { data, content } | split a --- YAML-ish block from the body | | toPlainText | (src) => string | strip Markdown to readable prose | | excerpt | (src, { length?, suffix? }?) => string | word-boundary plain-text preview | | sanitizeHtml | (html, { allowedTags?, allowedAttributes? }?) => string | clean untrusted HTML |

Security

markdownToHtml is safe by default: any raw HTML found in the Markdown source is escaped, never passed through, so injected <script> / <img onerror> cannot execute. href/src URLs are scheme-checked — only http, https, mailto, tel, fragments and relative paths survive; javascript: / data: / vbscript: (including control-char-obfuscated variants) become #. There is intentionally no "allow raw HTML" mode — if you need to display HTML from an untrusted source, run it through sanitizeHtml first. sanitizeHtml is a pragmatic, dependency-free regex sanitizer (removes script/style/iframe/etc., on* handlers, and dangerous URLs); for hostile input in a browser you may still prefer a full DOM-based sanitizer.

Perfect for a Markdown-powered blog — it's what the blog template in create-lacspace-app uses.

Licensing

Free under the Lacspace Free Licence — permissive freedoms. Use it in personal and commercial projects at no cost; just keep the notice. See the Lacspace Licence Centre.


The Lacspace Developer Platform

@lacspace/markdown is part of 80+ zero-dependency, isomorphic TypeScript packages — one standard library for the modern web. Explore the ecosystem:

  • 📦 This package, documented — https://developer.lacspace.com/packages/markdown
  • 🗂️ All 80+ packages — https://developer.lacspace.com/packages
  • 🧭 Developer handbook — guides & runnable recipes — https://developer.lacspace.com/handbook
  • 🧪 Live playground — run any package in your browser — https://developer.lacspace.com/playground
  • 🖥️ Finished app templates — https://templates.lacspace.com
  • 🚀 Scaffold a full app — npm create lacspace-app@latest

Free under the Lacspace Free Licence — a permissive, free-to-use licence.