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 🙏

© 2024 – Pkg Stats / Ryan Hefner

butterfloat

v1.5.0

Published

Knockout-inspired view engine for RxJS with TSX

Downloads

290

Readme

Butterfloat

Butterfloat is a Knockout-inspired view engine using modern ESM via Typescript and pure RxJS observables.

"The greatest view engine the web has ever seen."

Further documentation: Getting Started starts a gentle tour of Butterfloat features.

Knockout-inspired

Knockout left a lasting legacy in web development.

Like Knockout, Butterfloat is focused on providing a way to bind dynamic changes in a web view. It comes from a perspective that static DOM elements are more common and the "default" and that dynamic changes should be bound from Observables.

Butterfloat benefits from advances in Typescript, modern ESM, and RxJS since Knockout's best years.

TSX, but not a Virtual DOM

TSX in Typescript is a powerful compile-time type checked template language for HTML and similar trees. With TSX Butterfloat can provide a best-in-class development experience at a fraction of the budget of some other web views.

Butterfloat does not take a "Virtual DOM" approach, but it does try to preserve some "Virtual DOM"-like benefits such as easier component testing without a live DOM implementation/fill-in. Instead, Butterfloat takes a "static-by-default" approach to DOM building and only runs its components once (and only once) per component instance.

Butterfloat relies entirely on pure observables to signal changes to be made, and the power of Butterfloat is how it schedules those changes by default for you. It has no Virtual DOM diff/patch routines, it binds changes directly to DOM instances.

The only parts of a Butterfloat component that may change are Observables and Components, everything else is setup once and only once.

If you are interested in seeing pure observables used in a Virtual DOM, consider trying Cycle.js.

Pure, RxJS Observables

From an RxJS perspective, Knockout's Observables were more accurately Subjects. It was sometimes too easy to leak private state-changing APIs across API boundaries. There's nothing wrong with using Subjects to store tiny bits of "atomic" state, in an Observable world, but Butterfloat wants to help you better encapsulate public versus private views of that state. (This includes a handy utility wrapper around BehaviorSubject<T> named butterfly.)

Also, we all remember the magic of ko.computed, but with RxJS so much of the power is appropriate use of RxJS operators in smart pipelines. Butterfloat believes in doing the right things with RxJS operators and avoiding "magic" Observable state and change detection strategies like ko.computed was.

It's easy to see the legacy of Knockout in the way that its "Observables" (Subjects) continued to influence "Signals" and related ideas in later languages, and all sorts of "automated" and magic change detection and signal detection logic. Butterfloat tries to follow the other fork in the road of Knockout's legacy if it had lived up to the name Observable that it chose to use and tried for greater purity and more powerful usages of Observable scheduling and operators.

Next Steps

Getting Started can lead you through a gentle tour of Butterfloat features.

A Usage Example

A complex component with embedded state may look something like this:

import { ComponentContext, ObservableEvent, butterfly, jsx } from 'butterfloat'
import { map } from 'rxjs'

interface GardenProps {}

interface GardenEvents {
  rake: ObservableEvent<MouseEvent>
}

function Garden(
  props: GardenProps,
  { bindEffect, events }: ComponentContext<GardenEvents>,
) {
  const [money, setMoney] = butterfly(1)
  const [labor, setLabor] = butterfly(0)

  const moneyPercent = money.pipe(
    map((money) => money.toLocaleString(undefined, { style: 'percent ' })),
  )

  const laborPercent = labor.pipe(
    map((labor) => labor.toLocaleString(undefined, { style: 'percent' })),
  )

  bindEffect(events.rake, () => {
    setMoney((money) => money - 0.15)
    setLabor((labor) => labor + 0.3)
  })

  return (
    <div class="garden">
      <div class="stat-label">Money</div>
      <progress
        title="Money"
        bind={{ value: money, innerText: moneyPercent }}
      />
      <div class="stat-label">Labor</div>
      <progress
        title="Labor"
        bind={{ value: labor, innerText: laborPercent }}
      />
      <div class="section-label">Activities</div>
      <button type="button" events={{ click: events.rake }}>
        Rake
      </button>
    </div>
  )
}

This may look like React at first glance, especially the intentional surface level resemblance of butterfly to useState and bindEffect to useEffect. This exact example is refactored in ways that a React component can't be (moving the butterfly state to its own "view model") in the State Management documentation, but it is suggested you take the scenic route and start with Getting Started.

Other Examples

Example projects migrated from Knockout: