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

@cube-drone/marquee-markup

v0.4.0

Published

Marquee, batteries included: parse + render to HTML in one motion, fonts and styles along for the ride, plus the `marquee` CLI

Readme

@cube-drone/marquee-markup

The Marquee Markup Language.

Marquee source in, complete styled HTML out, with the stylesheet, fonts, and emoji table along for the ride.

This is the package you want unless you know why you want a smaller one.

npm install @cube-drone/marquee-markup
import { marquee } from "@cube-drone/marquee-markup";
import { readFileSync, writeFileSync } from "node:fs";

writeFileSync("hello.html", marquee(readFileSync("hello.mq", "utf8")));

To learn the language, read WRITING.md or see it live at marquee.cube-drone.com. This document is the contract for the tools.

The CLI

npx marquee <file.mq>                 render one page to stdout
npx marquee <file.mq> -o out.html    ...or to a file
npx marquee <site-dir> <out-dir>      build a whole site

| flag | effect | |---|---| | --nofetch | skip the network fetch-ahead pass. Default is ON: bare web links become real OpenGraph preview cards, fetched once at build time. With --nofetch, unrecognized links stay plain links and the build touches no network | | --envelope | wrap plain documents in a 650px centered column for readability. Documents whose top-level content is :::page directives are left alone (the author took layout control) | | --darkmode | force color-scheme: dark. Default: pages follow the reader's OS theme | | --noreadable | disable the color-readability rescue (see readable below). Default is ON for pages |

Every flag is a library option first; the CLI is sugar over the functions below.

Functions

What Happens if the Marquee is unparseable?

I have good news for you: if you give it text you are very likely to be in the clear: this language is designed to always be parseable. any input renders: nothing throws.

The only exception in the whole API is an unknown dialect version declaration (#!marquee 99), which throws UnsupportedVersionError — so that old versions of Marquee don't try to parse documents it doesn't understand yet.

marquee(source, options?) => string

  • parse
  • render
  • inline the stylesheet
  • inline exactly the font faces the page actually uses (as base64, by default)
  • wrap in a complete <!doctype html> page shell.

The returned string is self-contained (thanks to all of the inlining): you should be able to deliver it as a complete .html file.

Turbolinks (bare URLs alone in a paragraph) render through the fetchless plugin chain: which is to say, we don't go chasing after OpenGraph data to expand them.

YouTube/Spotify links become embeds (these won't work if you don't serve the file from somewhere),

image/audio/video links become the media, everything else degrades to a plain link.

marqueeFetch(source, options?) => Promise<string>

In the above function we didn't fetch OpenGraph data (how could we? we were running in sync mode). marqueeFetch does the same thing but it does, in fact, go fetch that OpenGraph data!.

marquee() plus the network: before rendering, runs every composed turbolink plugin's async resolve() phase over the document's link targets — concurrently across targets, with the OpenGraph plugin joining the chain automatically (10-second timeout per fetch). Gathered summaries render as preview cards; failed or timed-out fetches degrade to plain links.

Trust contract: this function executes plugin fetch code. The default chain is safe; if you pass your own turbolink plugins, you are vouching for them. Rendering itself remains synchronous and fetchless — all network happens in the resolve phase, before render, never during.

marqueeFragment(source, options?) => { body, css, title, fontTokens }

The pieces, for embedding in your own page:

  • body: string — one <div class="mq-doc">…</div> fragment
  • css: string — everything the body needs styled: the Marquee stylesheet, the composed plugins' skins, and font faces per the fonts option
  • title: string — the title option, else the document's :::meta title, else "Marquee"
  • fontTokens: string[] — which font faces the body actually wears (feed to fontFilePath() if you're hosting font files yourself)

marqueeBody(source, options?) => string / marqueeHead(source, options?) => string

The fragment, pre-split for template stitching: marqueeBody returns just the body fragment; marqueeHead returns <title>…</title>\n<style>…</style>, paste-ready for a <head>.

buildSite(siteDir, outDir, options?) => SiteReport

A folder of .mq files in, a static website out. The contract, precisely:

  • Every <id>.mq becomes <id>.html, except files named _*.mq, which are partials: includable via :::include doc=_nav::: but not rendered as pages. Includes resolve beside the including file; included documents may not themselves include (cycles are therefore unrepresentable); a missing include renders a visible placeholder, never an error.
  • Relative doc-id links resolve to built pages: [Menu](menu) becomes href="menu.html".
  • Relative media is copied into <out>/media/ (deduplicated, name-collision-safe) and embeds re-point to the copies. Remote (https:) media is left as-is: readers fetch it.
  • Fonts ship as real files: only the faces the site's pages actually use are copied to <out>/fonts/, with a generated css/fonts.css pointing at them — cacheable across pages, never base64.
  • Stylesheets are files: css/marquee.css and css/turbolink.css (the composed plugin chain's skins), linked from every page shell.
  • Page titles come from each document's :::meta title, falling back to the file's id.
  • Returns { pages: string[], mediaFiles: number, fontFaces: string[], outDir: string }.

buildSiteFetch(siteDir, outDir, options?) => Promise<SiteReport>

buildSite() with the fetch-ahead pass: gathers turbolink targets across all the site's .mq files (partials included), resolves them once, builds with the results. Same trust contract as marqueeFetch.

Options

MarqueeOptions — every field optional:

| option | type | default | contract | |---|---|---|---| | title | string | document's :::meta title, else "Marquee" | the page <title> | | fonts | "inline" \| "external" \| "none" | "inline" | inline: used faces embedded as base64 (self-contained page). external: @font-face rules point at <fontBase><token>.woff2 — you copy the files (fontTokens names them, fontFilePath() locates them). none: no faces; font names degrade to their fallback stacks | | fontBase | string | "fonts/" | URL prefix for fonts: "external" | | emoji | Record<string, string \| { image, alt? }> | {} | your emoji table, layered over the defaults; yours win on collision. String values are replacement text; { image, alt? } renders a character-sized inline <img> (the custom-emoji mechanism — the image URL is embedder-trusted, like every hook) | | emojiDefaults | boolean | true | the standard gemoji table (:sparkles: and ~1,900 friends) loads implicitly. false: unlisted shortcodes stay literal :slug: | | colorScheme | "light" \| "dark" | follow the reader's OS | forces the page shell's color-scheme. Applies to whole pages only; fragments follow their host | | envelope | boolean | false | wrap the document in a 650px centered readability column. Defers to documents that are a :::page | | readable | boolean | true for pages, false for fragments | the color-readability rescue: author colors keep their hue but their lightness is clamped toward the canvas's opposite (via CSS relative color syntax), so dark-red text survives dark mode. Containers that paint their own background are left alone; browsers without support see raw colors. Fragments default off because a host theming by class rather than OS preference would get the clamp backwards | | plugins | TurbolinkPlugin[] | the fetchless default set | the turbolink chain, in priority order. In fetch mode, opengraphPlugin is appended unless already present | | profile | Partial<Profile> | — | override any embedder policy hook (allowed URL schemes, media resolution, emoji, directives). Wins over everything above |

SiteOptions (for buildSite/buildSiteFetch) accepts emoji, emojiDefaults, colorScheme, envelope, readable, plugins, and profile with identical meanings, applied per-page.

Safety, stated plainly

  • Author bytes never reach output except through escaping; targets only through the profile's scheme allowlist; the AST is the contract — nothing innerHTML-shaped exists.
  • Unknown vocabulary degrades visibly (placeholders, literal text) and never eats content.
  • The rendered page contains zero JavaScript. Effects are CSS, honor prefers-reduced-motion, and reveal effects cannot hide text where animations don't run.
  • The only code execution surface is plugin resolve() in the fetch functions, and only for plugins you composed.

Reaching deeper

Everything underneath is re-exported, so outgrowing the convenience never means switching packages: parse and the AST types (Node, Attrs, Reason), render / renderMarquee / bareWebProfile / Profile and the escapes, marqueeCss, standardEmoji, the font helpers (FONT_MANIFEST, fontFilePath, inlineFontFaces, externalFontFaces), and the whole turbolink toolkit (TurbolinkPlugin, composeTurbolinks, resolveTargets, turbolinkTargets, turbolinkStyles, renderCard, defaultPlugins, opengraphPlugin). The leaner-diet packages behind them: @cube-drone/marquee-parser, -html-renderer, -css, -fonts, -emoji, -turbolink — same code, à la carte. This package deliberately includes the ~1.3MB font grab bag; that's what batteries-included means.

Same version number across every package and the Rust crates = passed the same published conformance corpus, by definition. License: MPL-2.0.