Stimulus.js: Do We Really Need Another Javascript Framework?

A key on a keyboard labeled Blog

Imagine you’re building a sort of classic, minimally-interactive, text-driven website, like a blog. You respect the web and understand that it’s at its best when it is comprised of interlinked hypertext documents, so you decide you want to serve this site to your visitors as just regular old HTML. It’s not perfectly static, and there are some interactions that don’t happen strictly on the seams of changing URLs, so you add a little Javascript. It’s some vanilla event-binding, DOM-manipulation stuff, for the handful of accordions or carousels or whatever that this design calls for. Maybe you decide to organize your event-binding logic into a series of views, separating your selectors from your functions, and it might make sense to use Backbone for this—even in 2018, this is a totally valid use case! You might even add a build step for bundling your Javascript so you can use new language features and split up your files, but you know that, at its core, this website needs to serve mostly-static hypertext.

ASCII art of a butler holding a tray labeled PJAX

It’s working well, it’s pretty easy to develop, and since there’s not a whole lot of render logic, it’s pretty stable, but you have a problem—it’s not very fast. You’re sending those bytes to your visitors as quickly as you can, but you’re thinking maybe you can speed up those page loads with some more Javascript. You’ve already got your whole thing built, plus you have this ideological bent on serving HTML, so you don’t want to resort to client-side rendering. There’s an old, stable library called Turbolinks that’s just what you need—it listens for navigation events and takes over for the browser, requesting the destination URL asynchronously and replacing the current document’s body, and it avoids a lot of time spent loading and rendering. This helps a lot, but it breaks your existing Javascript: when you load a new page, the DOM changes, but your code doesn’t re-execute, so your event bindings are lost. You might modify your code to re-bind its events whenever Turbolinks finishes manipulating the DOM, being careful not to re-re-bind to old cached fragments when your user hits the back button, and this is okay but it’s very fussy. You find yourself spending more time thinking about render lifecycles than finishing your project.

A screenshot of sample Javascript from the Stimlus.js website

You get the sense that what you’re doing really isn’t all that special and you start searching for prior art. This search leads you to Stimulus! It’s made by Basecamp, the people who made Turbolinks, and while the two libraries are independent, they’re also designed to work together really nicely because they both satisfy this same ethos of letting your HTML do its thing and layering extra functionality on top of it when appropriate—this used to be called progressive enhancement but I don’t think anyone really uses that term anymore. Stimulus introduces a pretty major change to the client-side code you’ve already written for this project; instead of writing selectors in your Javascript to bind events and fetch nodes, Stimulus asks you to add some data-* attributes to that markup you already have. It also allows you to organize those functions into classes that share selectors and have their own lifecycles, which handle binding for you automatically and naturally know what to do about the Turbolinks cached-markup use case. The mount-render-bind separation feels a lot like all the good parts of using JSX with React, except you don’t need to write your whole view layer in Javascript.

A screenshot of sample HTML from the Stimlus.js website

Stimulus’s major responsibility is to walk through the DOM, find your data-controller and data-action attributes, and use those to link their respective nodes to the Javascript controllers you’ve loaded to your page. It triggers lifecycle events on each one—initialize, mount, unmount—then listens for events like clicks, hovers, and submits, matching those up with whatever functionality you’ve defined. It uses data-target attributes to allow you to target specific elements on the page without tying your Javascript and HTML together with CSS-style selectors. Your HTML just needs to know that this button or that paragraph needs to be exposed, and your Javascript just knows that something will be accessible. It’s like a React ref, but with fewer steps. Stimulus encourages you to store your state directly in the DOM, and has a first-class set of APIs for reading and writing controller-specific data in…data-* attributes! This means that your HTML can talk to your Javascript, and vice versa, without either of them really needing to know what the other is doing.

A series of tubes

This separation of concerns is the whole point of Stimulus—it asserts that HTML is for content, CSS is for presentation, and Javascript is for interaction. Each of these technologies is really good at its respective responsibility, but the boundaries between them have always been fuzzy. Solutions like React attempt to solve these problems by essentially throwing everything out and starting over, giving the developer tools that look like HTML or CSS or DOM scripting but are actually something quite different. Stimulus (and Turbolinks, for that matter) accept that HTML—and, by extension, HTTP—are still really good at what they do, and that we don’t need to throw them out or bury them in abstractions to achieve the experiences a modern user demands. If you ask me if we need another Javascript framework, my answer is no. Javascript has finally gotten really good at doing the thing it was originally designed to do. What we need are conventions that empower us to get out of the web’s way, and I think Stimulus is a really good one.

Written August 1st, 2018 by John Holdun