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

@silarhi/turbo

v0.3.0

Published

Framework-agnostic Hotwire Turbo lifecycle orchestrator — mount/unmount your listeners across Turbo Drive, Frames and Streams

Readme

@silarhi/turbo

npm version license

A tiny, framework-agnostic Hotwire Turbo lifecycle orchestrator.

TurboHandler wires the Turbo Drive / Frame / Stream lifecycle to a single pair of onMount / onUnmount callbacks, so your per-container listeners (tooltips, selects, datepickers, …) initialise and clean up correctly across every Turbo navigation — including Stream / Mercure mutations and morphs, which fire no render event of their own.

Installation

yarn add @silarhi/turbo
# or: npm install @silarhi/turbo

@hotwired/turbo is a peer dependency — you already have it in a Turbo app.

Usage

import { TurboHandler } from '@silarhi/turbo'

const handler = new TurboHandler({
    onMount: (container) => app.mount(container),
    onUnmount: (container) => app.unmount(container),
})

handler.start() // attach every listener (idempotent)
// handler.stop() // detach them all and reset

onMount runs on every freshly rendered or inserted container; onUnmount runs before one leaves the DOM. Point them at whatever initialises and tears down your widgets — TurboHandler decides when and on which element, you decide what.

Options

| Option | Type | Default | Purpose | | ----------------- | ------------------------------------------ | ------------------ | ------------------------------------------------------------------------------------ | | onMount | (container: Element \| Document) => void | — | Init your listeners on a freshly rendered / inserted element. | | onUnmount | (container: Element \| Document) => void | — | Tear them down before the element leaves the DOM. | | getContainer | (document: Document) => Element | document.body | Root element a full Drive render mounts on. | | onRedirect | (url: string) => void | Turbo.visit(url) | Follow a server Turbo-Location redirect. | | streamMutations | boolean | true | Diff the DOM around each Turbo Stream render and mount/unmount the nodes it touched. | | morphMutations | boolean | false | Handle Drive/Frame morph renders per-node via turbo:morph-element. |

start() and stop() attach and detach all document listeners and are both idempotent.

streamMutations (Turbo Stream morphs)

By default the handler observes the DOM around every turbo:before-stream-render, then mounts the nodes a Stream inserted and unmounts the ones it removed. It is morph-aware: a reused node may be reported as both removed and added, and the policy is mount-wins (never unmount-then-remount, never double-mount). Pass streamMutations: false to opt out and keep stock Turbo behaviour.

morphMutations (Drive & Frame morphs)

streamMutations covers Turbo Stream morphs (<turbo-stream method="morph">). The other two morph paths — Drive page refresh (<meta name="turbo-refresh-method" content="morph">) and frame refresh (<turbo-frame refresh="morph">) — fire turbo:before-render / turbo:before-frame-render with renderMethod: "morph" and morph the container in place. By default the handler treats them like a replace: it unmounts then re-mounts the whole container, re-initialising listeners even on the nodes the morph preserved.

Set morphMutations: true to instead skip that coarse re-init on morph renders and refresh only the nodes Turbo actually morphed, via the turbo:morph-element event — leaving preserved widgets (an open select, a focused field) untouched. Scope: it covers elements morphed in place; subtrees a morph adds or removes wholesale aren't re-mounted by this path. It requires onMount / onUnmount to be safe to call on an individual element.

Symfony / PHP companion

The matching PHP package silarhi/turbo-bundle handles the server side — it turns a redirect issued inside a Turbo Frame into a 204 + Turbo-Location (escalating it to a full Drive visit, the contract onRedirect consumes) and ships a turbo_frame Twig filter. The two halves are usable independently.

License

MIT © SILARHI