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

@revanta/mumol

v0.1.1

Published

A modern protein and molecule visualization library.

Readme

MuMol

MuMol is a small, framework-agnostic TypeScript library for parsing and visualizing proteins and molecules in the browser. Its renderer is built exclusively on Three.js WebGPU and the Three.js Shading Language (TSL): there is no WebGL renderer, shader string, or non-node material fallback.

MuMol is at an early 0.x stage. Its API can change while the core rendering and structure model settle.

Features

  • Strict WebGPU initialization with an explicit GPU adapter and device
  • Physical, toon, Phong, flat, and x-ray TSL node-material shaders
  • Plastic, glass, metal, ceramic, and velvet presets with live material controls
  • Cartoon ribbons, backbone, wireframe, ball-and-stick, and space-filling views
  • Structure, chain, CPK element, B-factor, rainbow, and pLDDT colors
  • Instanced atom and split-color bond geometry, including multiple bond orders
  • PDB and mmCIF parsing with models, alternate locations, and secondary structure
  • XYZ parsing and spatially partitioned covalent-radius bond inference
  • TSL depth of field, bloom, ambient occlusion, silhouette outline, and GPU ASCII post-processing
  • Orthographic and perspective cameras, switchable at runtime
  • Camera-following studio light rig with room-environment image-based lighting
  • Click/API selection at atom, residue, and chain levels with highlight and focus
  • Background-colored depth cueing with tighter fog for selection focus
  • Mouse, touch, and trackpad orbit, zoom, and pan controls
  • Demand-driven rendering that sleeps while a static view is idle, with a configurable frame-rate cap and reduced pixel ratio during interaction

The feature model follows 001TMF/ProteinView, and the cartoon geometry semantics (spline tension, direction frames, and ribbon dimensions) follow molstar. Shallow reference checkouts are kept locally under vendor/ during development.

Requirements

  • A browser that exposes WebGPU through navigator.gpu
  • A secure context (https:// or localhost)
  • three 0.178 or newer as a peer dependency
  • Node.js 22.13 or newer for development

MuMol deliberately fails with WebGpuUnavailableError when WebGPU is not available. It acquires the GPU device itself and disables Three.js's optional WebGL backend switch before renderer initialization.

Install

npm install mumol three
npm install -D @types/three   # TypeScript projects

Three.js is a peer dependency so applications control its version and bundlers ship exactly one copy. @types/three is an optional peer for TypeScript consumers; match its version to the installed three.

Quick start

<canvas id="molecule" style="width: 640px; height: 480px"></canvas>
import { MuMolViewer, parsePdb } from "mumol";

const response = await fetch("/structures/1crn.pdb");
const molecule = parsePdb(await response.text());
const canvas = document.querySelector<HTMLCanvasElement>("#molecule");

if (canvas === null) {
  throw new Error("Missing molecule canvas");
}

const initialization = new AbortController();
const viewer = await MuMolViewer.create(
  canvas,
  {
    representation: "cartoon",
    colorScheme: "structure",
  },
  { signal: initialization.signal },
);
viewer.setMolecule(molecule, {
  fit: { padding: 0.15, rotationSafe: true },
  view: { rotation: [-0.3, 0.5, 0], zoom: 1.2 },
});

// Later, when the surrounding UI unmounts:
viewer.dispose();

MuMolViewer.create() is the recommended construction path. It resolves only after the WebGPU renderer is ready, so adapter, device, and renderer failures reject through a normal try/catch. If its signal is aborted during initialization, it rejects with the signal's reason and disposes the partially constructed viewer. Once create() resolves, call dispose() to end the viewer's lifetime as usual.

The constructor remains available for applications that need to prepare the viewer while the browser acquires its GPU device. In that form, await ready explicitly and dispose after an initialization failure:

const viewer = new MuMolViewer(canvas, options);
try {
  viewer.setMolecule(molecule);
  await viewer.ready;
} catch (error) {
  viewer.dispose();
  throw error;
}

Representations and colors

viewer.setRepresentation("ball-and-stick");
viewer.setColorScheme("element");
viewer.setSheetArrowheads(false);
viewer.setShaderStyle("toon");
viewer.setMaterialPreset("glass");
viewer.setMaterialSettings({ roughness: 0.12, transmission: 0.8 });
viewer.setAutoRotate(true);

Representations are cartoon, backbone, wireframe, ball-and-stick, and spacefill. Color schemes are structure, chain, element, b-factor, rainbow, and plddt.

Camera, lighting, and performance

viewer.setCameraMode("perspective"); // or "orthographic" (default)
viewer.setBackground("#0f172a");
viewer.setEnvironmentIntensity(0.45); // room-environment IBL; 0 disables it
viewer.setMaxFrameRate(60); // Number.POSITIVE_INFINITY uncaps

A hemisphere/key/fill light rig follows the camera, so shading stays consistent from every orbit angle. Physical materials additionally pick up reflections from a generated room environment scaled by environmentIntensity. While the user orbits, zooms, or auto-rotation is active, rendering drops to interactionPixelRatio (default 1.5) and is capped at maxFrameRate (default 60); a static view stops rendering entirely.

Effects and selection

All effects are assembled with Three.js's WebGPU RenderPipeline and TSL nodes:

viewer.setPostProcessingSettings({
  depthOfField: 0.35,
  bokehScale: 2,
  bokehQuality: "high",
  bloomStrength: 0.2,
  outlineThickness: 0.002,
  aoIntensity: 0.6,
  asciiEnabled: true,
  asciiCellSize: 14,
});

const stopFrameRateUpdates = viewer.onFrameRateChange((framesPerSecond) => {
  console.log(
    framesPerSecond === 0
      ? "Rendering is idle"
      : `${Math.round(framesPerSecond)} FPS`,
  );
});

viewer.setSelectionMode("residue");
viewer.setSelectionHighlightStyle("glow");
const selected = viewer.selectResidues([{ chain: "A", sequence: 42 }]);
if (selected.residues.length === 0) {
  console.warn("Residue A:42 was not found");
}
viewer.zoomToSelection();

const selectionController = new AbortController();
viewer.onSelectionChange(
  ({ selection, previousSelection, source }) => {
    console.log(source, previousSelection.atomIndices, selection.atomIndices);
  },
  { signal: selectionController.signal },
);

// Later, during cleanup:
selectionController.abort();
stopFrameRateUpdates();

Bokeh quality can be balanced (75% blur resolution), high (full resolution), or ultra (full-resolution dual reconstruction). balanced is the default; ultra offers the smoothest bokeh at the highest GPU cost.

Effects are only wired into the render pipeline while active, so disabled effects cost nothing. outlineThickness draws a screen-space silhouette ink from depth discontinuities, and aoIntensity enables ground-truth ambient occlusion (GTAO) with a denoise pass; while ambient occlusion is active the scene pass renders without multisampling, the usual trade for screen-space AO.

Selections can also be set with selectAtoms(), selectChains(), or the general setSelection() query API. Clicking the canvas uses the active selection mode. Selection callbacks and the mumolselectionchange canvas event report the previous and resulting selections plus a pointer, api, or molecule source. Selection methods return the resulting frozen selection, so an empty match is directly detectable. setFog() controls background-colored depth cueing across the structure, with a tighter fog range for close-up selection views.

The initial fit, selection, and view can be applied atomically when replacing the molecule. Invalid nested options leave the previous molecule in place, and selection listeners are notified only once with the resulting selection:

viewer.setMolecule(molecule, {
  fit: { padding: 0.15, rotationSafe: true },
  selection: { residues: [{ chain: "A", sequence: 42 }] },
  view: { zoom: 0.9 },
});

padding is a non-negative fractional margin. rotationSafe uses an enclosing sphere rather than the current projected bounds, trading a looser fit for one that remains fully visible while orbiting.

Recording a turntable video

const video = await viewer.recordTurntable({
  durationSeconds: 12,
  fps: 60,
  onProgress: (fraction) => {
    console.log(`${String(Math.round(fraction * 100))}%`);
  },
});
// `video` is a `video/mp4` Blob ready to download or upload.

recordTurntable() renders one full rotation frame by frame and encodes it with WebCodecs into an H.264 MP4. Because every frame is rendered at an exact angle and encoded with an exact timestamp, the output has no dropped or duplicated frames and loops seamlessly. Each frame renders and is captured inside one animation-frame callback — the only phase where a WebGPU canvas is reliably readable — so a recording takes frames / refresh rate of wall-clock time (faster than real time on high-refresh displays) and the tab must stay visible while it runs. Recording always uses the full pixel ratio (pass pixelRatio to go higher, up to 4), and user input is suspended until the returned promise settles. The mp4-muxer dependency is loaded on demand, so applications that never record never ship it.

Parsing without a viewer

The structure model and parsers do not touch browser APIs, so they can be used in Node.js:

import { getMoleculeBounds, parseMmcif, parseXyz } from "mumol";

const water = parseXyz(`3
water
O 0 0 0
H 0.9572 0 0
H -0.239987 0.927297 0
`);

console.log(water.bonds, getMoleculeBounds(water));

const response = await fetch("https://example.test/structure.cif");
const cifMolecule = parseMmcif(await response.text());

All parser results are immutable snapshots. PDB topology is read from CONECT records when present and inferred from covalent radii otherwise.

Development

npm run dev
npm run test:run
npm run typecheck
npm run build
npm run example:build

The basic example is a responsive Preact and Tailwind CSS application served at the URL printed by Vite. It exercises structure loading, every representation and color mode, TSL materials and post-processing, selection, focus, and mobile controls. Test coverage focuses on chemistry, immutable model behavior, and structure parsing; the production build verifies the WebGPU/TSL module graph and declaration output.