Introducing reactive.coffee: reactive programming and declarative UIs in CoffeeScript

(Update: see also the Hacker News thread)

(Cross-posted from the Infer Engineering Blog)

At Infer, our engineering team faces no shortage of technical challenges, but it’s not all just about data lifting and machine learning. Everything always comes back to the user, whether it’s helping companies better understand their customers through our dashboards, or providing the visual tooling necessary for our own team to build models more productively. We build these out as web-based user interfaces of varying complexity.

Today we’re announcing the release of a new tool that will hopefully make building these web app frontends a bit more expressive and fun. reactive.coffee is a small, lightweight CoffeeScript library that provides two sets of functionality:

  • a set of primitive data structures for reactive programming an HTML template
  • DSL with for building dynamic, reactive web UIs

Why another client-side framework? Surely with Angular, Ember, Knockout, and a long tail of other libraries, there must be something else that we could have run with!

In fact, we come from having used these other frameworks on various projects in the past. While we have deep respect for these trailblazers, we wanted to capture some of the lessons we learned from our own experiences. Our key goals with reactive.coffee are:

  • simplicity: minimize magic and have foundations in a small set of primitives/concepts
  • scalability: in terms of both performance and application architecture

Simplicity

reactive.coffee provides some data structures like observable cells that form the building blocks of the reactive programming style. The bind function is the central mechanism through which you can declaratively create cells that react to other cells. The library handles all the event listeners and subscription management for you. For example:

x = rx.cell(3)
y = rx.cell(5)
z = bind -> x.get() + y.get()
z.get() # 8
x.set(1)
z.get() # 6

The UI layer builds on top of this—it lets you create DOM elements whose attributes/contents are bound to cells (which in turn may represent the application data model). For instance, we can declare that we want a scoreboard of players and their picture, name, and score, further styling each player based on whether they’re beating the current player:

ul {class: 'scoreboard'}, players.map (player) ->
  li {class: bind -> 'winning' if player.score.get() > myScore.get()}, [
    img {class: 'profile-pic', src: "/images/#{player.img}.png"}
    div {class: 'profile-name', click: -> showPlayer(player)}, "#{player.name}"
    div {class: 'score'}, bind -> "#{player.score.get()}"
  ]

With this declarative template approach, reactive.coffee will ensure that the view always reflects the data it’s bound to, even as the players and scores change. And it does so efficiently, propagating events and manipulating the DOM only where needed.

That’s it! A main idea behind Reactive was to keep everything very lightweight. We’d be hard-pressed to call this a framework. We believe in using best-of-breed libraries for different concerns, so Reactive doesn’t try to also do URL routing, for instance—there are dedicated libraries like Crossroads that do this well. Frameworks that mandate their own way of doing things certainly have their place, but as soon as you stray outside the boundaries, more flexible tools come into their own.

We also want to minimize magic and moving parts. There’s no new languages/syntax, no dependency injection magic, no new directives or compiler subsystems. We believe in embedding within a full programming language for templating, which lets us leverage/re-use the host language’s expressivity and existing code base, rather than reinvent a new template language. CoffeeScript is a great host for domain-specific languages. No need for new looping/binding control structures, no new expression filter syntax or definitions, no “logic-less” templates. It’s all just CoffeeScript. This is optimized for developers and doesn’t pretend to be something that can be wielded effectively by designers.

Scalability

Being able to break down an application into reusable, well-encapsulated components is the key to managing complexity. This goes beyond defining view templates—components encapsulate not just the view but also behaviors. Reactive makes it extremely easy to define components, since they’re just functions that return views, with behaviors attached via bindings and closures.

To continue with the above scoreboard example, we can simply wrap it all into a function:

scoreboard = ({players, showPlayer}) ->
  ul {class: 'scoreboard'}, players.map (player) ->
    ...

and then use it from anywhere else:

shownPlayer = rx.cell(...)
div {class: 'side-panel'}, [
  h1 'Players and Scores'
  input {type: 'text', placeholder: 'Your name', change: -> ...}
  scoreboard {players: ..., showPlayer: (player) -> shownPlayer.set(player)}
  playerDetails {player: shownPlayer} # wire up another component
]

Since these are just functions, the template system very easily and naturally supports complex views as well, such as recursive templates (for rendering things like tree-structured views).

Furthermore, reactive.coffee scales not only in code architecture, but in performance. With various other frameworks, there may be wrangling necessary to keep the incremental re-evaluation/re-rendering efficient for views/models beyond a certain size or complexity. In Angular, for instance, you may need to pay very close attention to the scope apply/watch mechanics.

Our library is instead based entirely on reactive programming. The bind machinery offers precise and intuitive control over how the view is updated, so that you re-render only the pieces that matter—no need to compute diffs between models or views. This goes beyond the databinding to tie your views to some model—you can use it to structure your entire application’s dataflow. Despite its simple building blocks, reactive.coffee has scaled to applications as complex as WYSIWYG web page editors. Here, the library is used to both model and render the web page being edited, which can be a large and complex data structure including the elements, attributes, styles, contents, etc., and which is incrementally editable in-place by the user.

Try it out!

Check out the project homepage which features complete tutorials, documentation, design rationale, and more. It’s also super-easy to get started with: the project is in bower and on cdnjs, and you can find examples on jsFiddle.

We’ll follow up with more in-depth posts comparing reactive.coffee with other client-side libraries/frameworks and walking through example applications including the ubiquitous TodoMVC task list.

In the meantime, we hope you find reactive.coffee to be a helpful addition to your client-side development toolbox!

Infer loves open source and is hiring! We’re looking for bright, passionate individuals to join our world-class engineering team. Learn more

Follow me on Twitter for stuff far more interesting than what I blog.