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

@voidberg/quarto

v0.4.0

Published

Generate EPUB3 and Kobo kepub files from HTML — runtime-agnostic, in-memory, with optional table of contents.

Readme

Quarto

Quarto

npm JSR CI MIT License

a book size of about 9½ x 12 inches (24 x 30 centimetres), determined by folding printed sheets twice to form four leaves or eight pages.

Generate EPUB3 and Kobo kepub files from HTML - entirely in memory, with no native dependencies and no external binaries.

import { writeFile } from "node:fs/promises";
import { generateEpub, toKepub } from "@voidberg/quarto";

const epub = await generateEpub({
  title: "On the Shortness of Life",
  author: "Seneca",
  chapters: [{ title: "I", html: "<p>It is not that we have a short time to live...</p>" }],
});

await writeFile("seneca.epub", epub);            // or Deno.writeFile, Bun.write...
await writeFile("seneca.kepub.epub", toKepub(epub));

Why another EPUB library?

Quarto came out of specific needs in my own projects (like instakobo):

  • Skip the table of contents, which for articles and newsletters is just noise
  • Generate kepubs without relying on kepubify
  • Work in the browser

Features

[!NOTE] Quarto is battle-tested on web articles - it's been validated against nearly a thousand real-world articles (with EPUBCheck), since that's the use case it was built for. It should handle full-length books just as well, but this is not something that I have tested extensively. If you run into problems, please open an issue.

  • Valid EPUB3 (verified against EPUBCheck in CI)
  • Optional table of contents (includeToc)
  • Native kepub conversion (toKepub) - Kobo reading-location spans, no binary needed
  • In-memory: returns a Uint8Array, never touches the filesystem
  • Runtime-agnostic: Node, Deno, Bun, Cloudflare Workers, and the browser (Web APIs + fflate)
  • Re-serializes messy HTML into well-formed XHTML for you
  • Downloads and embeds remote images so the book is self-contained
  • Modern ESM-only (Node >= 18; require()-able from CommonJS on Node >= 20.19 / 22)

Used by

  • instakobo - Read and annotate (and sync back) your Instapaper articles on your Kobo device
  • reSafari - Send webpages to your reMarkable tablet from Safari

Install

npm install @voidberg/quarto       # npm / pnpm / yarn
deno add jsr:@voidberg/quarto      # Deno (JSR)

API

generateEpub(input): Promise<Uint8Array>

| Option | Type | Default | Notes | |---------------------|------------------------|-----------------------|-------| | title | string | - | Required. | | chapters | Chapter[] | - | Required, at least one. | | author | string \| string[] | - | One or many creators. | | includeToc | boolean | true | false -> no visible TOC page. | | tocTitle | string | "Table of Contents" | Heading on the TOC page. | | cover | string \| Uint8Array | - | URL or raw bytes; generates a cover page. | | coverFromLeadImage| boolean | false | Promote a chapter's leading image to the cover (see below). | | coverBackground | string | reader default | CSS colour filling the cover's letterbox bands. | | language | string | "en" | BCP-47 tag. | | css | string \| false | bundled stylesheet | false ships no CSS. | | downloadImages | boolean | true | Embed remote <img> sources. | | transformImage | ImageTransform | - | Rewrite each image before embedding (see below). | | transformCover | CoverTransform | - | Compose/replace the cover before embedding (see below). | | series | string | - | Collection/series name (see below). | | seriesIndex | number | - | Position within the series. | | publisher | string | - | | | description | string | - | | | date | string (ISO-8601) | - | Pass for reproducible builds. | | id | string | derived (stable UUID) | Unique book identifier. | | fetch | typeof fetch | global fetch | Override for proxies/testing. |

A Chapter is { title, html, excludeFromToc?, author?, insertTitle? }. html is an HTML fragment - it does not need to be well-formed; Quarto parses and re-serializes it as valid XHTML. Set insertTitle: false to suppress the auto-generated <h1> heading and render only your markup.

toKepub(epub: Uint8Array): Uint8Array

Converts an EPUB (such as the output of generateEpub) into a Kobo kepub: every content document is rewritten with koboSpan reading-location markers and Kobo's book-columns / book-inner wrappers. Write the result with a .kepub.epub extension. No kepubify binary required.

DEFAULT_CSS: string

The bundled stylesheet, exported so you can extend rather than replace it.

imageSize(bytes, mime): { width, height } | undefined

Reads an image's pixel dimensions straight from its header - no decoding, no native deps. Supports PNG, GIF and JPEG; returns undefined for anything else or malformed data. Handy inside a transformCover to make layout decisions.

Images & covers

By default every <img> source (and the cover) is downloaded and embedded so the book is self-contained. Two hooks let you customize what gets stored:

  • transformImage(image) runs on each fetched image before the core-media-type check, so it can transcode an unsupported format (e.g. WebP/AVIF -> PNG for older e-readers), resize, or return null to drop the image.
  • transformCover(cover, meta) runs after the cover source is downloaded and passed through transformImage. It's called even when there's no cover source, so you can compose a designed cover from meta.title / meta.author alone. Return null for no cover.

Both receive/return RawImage ({ data: Uint8Array; mime: string }) and may be async.

import { generateEpub, imageSize, type CoverTransform } from "@voidberg/quarto";

const brandCover: CoverTransform = (cover, meta) => {
  if (cover && imageSize(cover.data, cover.mime)) return cover; // usable as-is
  return renderCover(meta.title, meta.author);                 // your designer -> RawImage
};

await generateEpub({
  title: "Field Notes",
  coverFromLeadImage: true,        // no cover? promote the article's leading image
  coverBackground: "#f4f1ea",      // blend the letterbox bands into the artwork
  transformCover: brandCover,
  chapters: [{ title: "Field Notes", html }],
});

coverFromLeadImage only promotes an image that appears before any text in the first chapter (and removes it from the body so it isn't shown twice); images that follow text are left in place.

Example: no table of contents

const epub = await generateEpub({
  title: "A Single Essay",
  includeToc: false,
  chapters: [{ title: "Essay", html: essayHtml, excludeFromToc: true }],
});

Series and collections

const epub = await generateEpub({
  title: "Part Three",
  series: "Engineering",
  seriesIndex: 3,
  chapters: [{ title: "C", html }],
});

series/seriesIndex are written as both EPUB3 belongs-to-collection metadata and the legacy Calibre calibre:series pair, so readers and device mods can group books. On Kobo, the NickelSeries mod reads this to shelve sideloaded books by series (stock firmware ignores it).

Development

npm install
git config core.hooksPath .githooks  # one-time: enable the pre-push test gate
npm test          # vitest
npm run test:workers # vitest in the Cloudflare Workers runtime (workerd)
npm run build     # tsc -> dist (ESM + d.ts)
npm run typecheck
npm run validate  # EPUBCheck (needs Java + EPUBCHECK_JAR)

The pre-push hook (in .githooks/) runs typecheck, lint, and tests before a push; the slower Workers and EPUBCheck suites run in CI. EPUBCheck runs in CI against generated fixtures to guarantee spec compliance.

Thanks to

License

MIT © Alexandru Badiu