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

@vectojs/markdown

v0.25.1

Published

Canvas-native Markdown (and TeX math) rendering for VectoJS: a Markdown entity plus CodeBlock, built on @vectojs/ui components with marked + @vectojs/tex.

Downloads

452

Readme

@vectojs/markdown

@vectojs/markdown renders Markdown (with TeX math) as a canvas-native entity tree: the Markdown entity parses with marked, typesets math through @vectojs/tex, and lays the result out using @vectojs/ui components. It was split out of @vectojs/ui precisely so the heavy marked + @vectojs/tex dependencies load only for apps that render documents — it sits above ui in the dependency graph and takes @vectojs/core and @vectojs/ui as peer dependencies.

Install

bun add @vectojs/markdown @vectojs/ui @vectojs/core

Peers: @vectojs/core >=1.34.0 <2, @vectojs/ui >=2.6.0 <3.

Usage

import { Scene } from '@vectojs/core';
import { Markdown } from '@vectojs/markdown';

const scene = new Scene(document.querySelector<HTMLCanvasElement>('canvas')!);
scene.renderMode = 'onDemand';

const md = new Markdown('# Hello\n\nInline math $E = mc^2$.', {
  maxWidth: 640,
  theme: 'githubDark', // or a full MarkdownTheme object
  onLinkClick(href) {
    console.log('navigate', href);
  },
});
scene.add(md.setPosition(24, 24));

// Stream LLM output: chunks coalesce into one parse per frame.
const stream = md.createStream({ incompleteMode: 'optimistic' });
stream.write('# Ti');
await stream.close(); // resolves after the final parse is applied

Highlights

  • CommonMark plus GFM tables, strikethrough, task lists, and autolinks; paragraphs become RichText, fences become CodeBlock, GFM tables become Table.
  • Frame-coalesced streaming via createStream(): at most one parse/layout commit per animation frame, grapheme-cluster-safe typewriter pacing (graphemesPerSecond), bounded-buffer backpressure on write(), onStable firing once when the document is final, and an incompleteMode of 'literal' (default) or 'optimistic' for unclosed markers mid-stream.
  • TeX math through @vectojs/tex, dynamically imported on the first formula so prose-only bundles stay small; preloadMathJax() / isMathJaxReady() control the lazy load, converted formulas are cached process-wide, and fenced math blocks typeset only once their closing fence arrives.
  • Themes: constructor accepts a preset name ('githubDark' | 'githubLight' | 'dracula' | 'solarizedDark' | 'solarizedLight') or a full theme object; post-construction changes go through setTheme() — theme itself is getter-only (#657).
  • Selectable content: rendered text projects browser-native drag selection, copy, and find-in-page; toggle at runtime with setSelectable(false).
  • Block affordances: opt-in copy/download controls on code blocks and tables with injectable writeClipboard / saveFile, per-kind overrides, and CSV export helpers (tableToCsv, tableToMarkdown).
  • Front matter: scanFrontMatter / parseFrontMatterFields expose YAML-ish metadata before the document body renders.
  • Large-document virtualization: pass virtualize and drive setVisibleRange(scrollY, viewportHeight) to materialize only top-level blocks near the viewport.

Images — imageResolver & CapGlyph adapter

Markdown maps every ![](src) through a single injectable resolver. The package has no CapGlyph import — that adapter lives in the app layer.

import type { ImageSource } from '@vectojs/ui';
import type { MarkdownImageResolver } from '@vectojs/markdown';

type MarkdownImageResolver = (src: string) => ImageSource | Promise<ImageSource>;
const defaultMarkdownImageResolver: MarkdownImageResolver = (src) => ({
  kind: 'url',
  url: src,
});

MarkdownOptions.imageResolver drives both block Image entities and inline InlineObject rasters (blob → createImageBitmap → ImageBitmap, with object-URL fallback). Sync and async resolvers both work; paragraphImage keeps a guessed 800×480 box until the resolver settles or the raster decodes, then reflows and scene.markDirty().

CapGlyph adapter (app layer):

import type { MarkdownImageResolver } from '@vectojs/markdown';

const imageResolver: MarkdownImageResolver = async (src) => {
  if (!src.startsWith('capglyph:')) return { kind: 'url', url: src };
  const cap = parseCapGlyph(src); // { endpoint, token, variant }
  const res = await fetch(cap.endpoint, {
    headers: { Authorization: `Bearer ${cap.token}` },
  });
  if (!res.ok) throw new Error(`CapGlyph fetch failed: ${res.status}`);
  const blob = await res.blob();
  const bitmap = await createImageBitmap(blob);
  return { kind: 'bitmap', bitmap }; // caller-owned; no blob: URL in a11y
};

const md = new Markdown(source, { maxWidth: 640, imageResolver });

Trust: Master never enters client — the resolver fetches only Derived Raster bytes (watermarked, capped size); CapGlyph is the credential/provenance signal, not a resize service. auto semanticMode keeps blob/bitmap off the shadow <img src>; Visual Flattening already removes the canvas hit-target and context-menu, so role is only needed when even a url must not appear in the a11y tree. See the Image reference for the full Visual Flattening trust table and DecodedImage lifecycle.

Projection policy — classifyProjectionBlocks & applyProjectionMode

Markdown documents negotiate visual projection per block: classifyProjectionBlocks(content) tags top-level blocks (prose for headings/paragraphs, code for fences, container for quotes/lists whose nested prose is tagged recursively, canvas for tables/chrome), and applyProjectionMode(root, mode) switches the subtree — 'canvas' forces canvas pixels, 'dom' materializes prose/code, 'hybrid' sets 'auto' everywhere so the engine negotiates per block.

Documents @vectojs/[email protected].

Documentation