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

@mvarble/mkdoc

v0.0.4

Published

Build a standalone webpage out of a single markdown or mdsvex document.

Readme

mkdoc

Build a webpage out of a single markdown or mdsvex document.

mkdoc dev notes/measures.md      # live-reloading server while you write
mkdoc build notes/measures.md    # a static page in build/measures/

The point is to get what a well-configured Vite project gives you --- KaTeX rendered at build time, syntax highlighting, images and scripts referenced relative to the document --- without a project. The template and the whole parser configuration are baked into the CLI, so a document is just a file somewhere on disk.

Getting it on your PATH

The simplest install is npm i -g @mvarble/mkdoc (or pnpm add -g @mvarble/mkdoc), which puts the mkdoc command on your PATH.

mkdoc is an ordinary npm bin, so any global install puts it there. While you are still tuning the parser configuration, link the working copy --- the command then runs the code in this checkout, and an edit to template/ or a pnpm build takes effect on the very next mkdoc run, with no reinstall:

pnpm build
pnpm link --global

For a frozen copy that does not follow the checkout:

npm i -g .          # copies, and runs `prepare` to build dist/ for you

Note that pnpm add -g <directory> links rather than copies, and does not run prepare --- run pnpm build first if you go that way. Uninstall with pnpm uninstall -g @mvarble/mkdoc.

Both land in a directory that is already on your PATH: pnpm uses $PNPM_HOME, npm uses its configured prefix. If node is managed by fnm or nvm, npm's prefix lives inside the active node version, so an npm i -g install disappears when you switch versions; pnpm's does not.

What a document gets

Math, through remark-math and KaTeX, rendered statically. A large table of macros is built in: \bfX for a bold X, \calF, \bbR, \rmalpha, and the same pattern for every letter and Greek letter. A document adds its own:

---
katex_macros:
    '\PP': '\mathbb{P}'
    '\EE': '\mathbb{E}'
---

Plain and single-quoted keys both work; double quotes do not, because a double-quoted YAML scalar processes escapes and "\PP" is an unknown escape sequence. katexMacros is accepted as a spelling of the same field.

An expression KaTeX cannot parse does not fail the build --- it is rendered in KaTeX's error colour so you can see which one it is, and mkdoc prints the message and the offending source.

Display equations are wrapped in a scroll container, so a long one slides sideways instead of overflowing a narrow screen.

Files next to the document. ![](./figure.png) works --- the URL is turned into a Vite import, so the image is hashed and emitted with the page. So do <script> imports in a .svx document, including other Svelte components.

Code, highlighted by Shiki in a light and a dark theme at once. A fence whose entire body is {./path/to/file} is replaced by that file's contents; add a range as {./path/to/file:10..20}.

Modules installed in mkdoc. A document sits wherever you keep it, with no node_modules above it, so a package has to be installed here and named before a document may reach for it:

pnpm add three
{
    "mkdoc": {
        "modules": ["three"]
    }
}

From then on any document imports it by name, the way it would in a project that had one:

<script>
    import * as THREE from 'three';
    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
</script>

Nothing is bundled for being on the list --- a module ships only with a page that actually imports it, and a document that imports none still builds to a page with no JavaScript in it. The list only decides what is reachable; see example/scene.svx for a working one.

Two things worth knowing. The <script> block is also what makes such a page ship its bundle, so no extra flag is needed. And the build renders the document under Node once, to produce the static HTML --- a library that touches window while it is being imported will fail there, and the way round it is to import() the library inside onMount instead, where it only ever runs in a browser.

Frontmatter. title, description, author, date and lang are used by the template. css names one or more stylesheets, relative to the document, that are loaded after mkdoc's own --- which is how you override anything about the look. hydrate decides whether the page ships JavaScript. Any other field is passed to the template as a prop.

Static by default

A plain markdown document has nothing to run, so the built page contains no JavaScript at all: the HTML, one stylesheet, and the KaTeX fonts. A document with a <script> block is assumed to have something interactive in it and gets a client bundle; hydrate: true or hydrate: false in the frontmatter, or --hydrate / --no-hydrate, settles it either way.

URLs in the built page are relative, so a static index.html opens straight off the filesystem --- double-click it, mail it, drop it on a USB stick. A page that ships JavaScript needs to be served over HTTP, because browsers refuse to load ES modules from file://. Pass --base /some/prefix/ if you are serving it from a subdirectory.

Options

mkdoc build <document>    write a static page (the default command)
mkdoc dev <document>      serve the document, rebuilding it as you type

  -o, --out <dir>     where to write the page   (default: build/<document>)
      --base <path>   URL prefix for the page's own files   (default: ./)
      --force         overwrite an output directory mkdoc did not write
  -p, --port <n>      port to listen on
      --host [addr]   expose the dev server on the network
      --open          open a browser once the server is up
      --hydrate       ship the client bundle, making the page interactive
      --no-hydrate    omit it, leaving a page that needs no JavaScript

Development

pnpm install
pnpm build                          # tsc, into dist/
node dist/cli.js dev example/measures.md

src/ is the CLI: document.ts reads the frontmatter that configures the build, modules.ts reads the mkdoc.modules list, vite.ts assembles the Vite config both commands share, and markdown/ holds the remark and rehype plugins. template/ is the page itself --- the HTML shell, the Svelte layout and the stylesheets --- and is consumed by Vite at runtime rather than compiled by tsc.