Over the last several months, I’ve been doing a bit of development using Buffalo, which is a rapid web development framework in Go, similar to Ruby on Rails. Like Ruby on Rails, the front-end layer is very simple: server-side rendered HTML with a bit of jQuery augmenting the otherwise static web-pages.

After a bit of time, I wanted to add a bit of dynamic flare to the frontend, like automatically fetch and update elements on the page. These projects were more or less small personal things that I didn’t want to spend a lot of time maintaining, so doing something dramatic like rewriting the UI in React or Vue would have been overkill. jQuery was available to me but using it always required a bit of boilerplate to setup the bindings between the HTML and the JavaScript. Also, since Buffalo uses Webpack to produce a single, minified JavaScript file that is included on every page, it would also be nice to have a mechanism to selectively apply the JavaScript logic based on the attributes on the HTML itself.

I since came across Stimulus, which looks to provide what I was looking for.

A Whirlwind Tour of Stimulus

The best places to look if you’re interest in learning about Stimulus is the Stimulus handbook, or for those that prefer a video, there is one available at Drifting Ruby. But to provide some context for the rest of this post, here’s an extreamily brief introduction to Stimulus.

The basic element of an application using Stimulus is the controller, which is the JavaScript aspect of your frontend. A very simple controller might look something like the following (this example was taken from the Stimulus home page):

// hello_controller.js
import { Controller } from "stimulus"
    
export default class extends Controller {
  static targets = [ "name", "output" ]
    
  greet() {
    this.outputTarget.textContent =
      `Hello, ${this.nameTarget.value}!`
  }
}

A controller can have the following things (there are more than just these two, but these are the minimum to make the controller useful):

  • Targets, which are declared using the static target class-level attribute, and are used to reference individual DOM elements within the controller.
  • Actions, which are methods that can be attached as handlers of DOM events.

The link between the HTML and the JavaScript controller is made by adding a data-controller attributes within the HTML source, and setting it to the name of the controller:

<div data-controller="hello">
  <input data-hello-target="name" type="text">  	
  
  <button data-action="click->hello#greet">Greet</button>

  <span data-hello-target="output"></span>
</div>

If everything is setup correctly, then the controllers should be automatically attached to the HTML elements with the associated data-controller annotation. Elements with data-*-target and data-action attributes will also be attached as targets and actions respectively when the controller is attached.

There is some application setup that is not included in the example above. Again, please see the handbook.

Why Stimulus?

Far be it for me to suggest yet another frontend framework in an otherwise large and churning ecosystem of web frontend technologies. However, there is something about Stimulus which seems appealing for small projects that product server-side rendered HTML. Here are the reasons that attract it to me:

  1. It was written by, and is used in, Basecamp, which gives it some industry credibility and a high likelihood that it will be maintained (in fact, I believe version 2.0 was just release).

  2. It doesn’t promise the moon: it provides a mechanism for binding to HTML elements, reacting to events, and providing some mechanisms for maintaining state in the DOM, and that’s it. No navigation, no pseudo-DOM with diffing logic, no requirement for maintaining a global state with reducers, no templating: just a simple mechanism for binding to HTML elements.

  3. It plays nicely with jQuery. This is because the two seem to touch different aspects of web-development: jQuery with providing a nicer interface with the DOM, and Stimulus with providing a way to easily bind to DOM elements declared via HTML attributes.

  4. That said, it doesn’t require jQuery. You are free to use whatever JavaScript framework that you need, or no framework at all.

  5. It maintains the relationship between HTML and JavaScript, even if the DOM is changed dynamically. For example, modifying the innerHTML by including an element with an appropriate data-controller attribute will automatically setup a new controller and bind it to the new elements, all without having you to do anything yourself.

    It doesn’t matter how the HTML gets to the browser, whether it’s AJAX or front-end templates. It will also work with manual DOM manipulation, like so:

    let elem = document.createElement("div");
    elem.setAttribute('data-controller', 'hello');
    
    document.append(elem);
    

    This allows a dynamic UI without having to worry about making sure each element added to the DOM is appropriately decorated with the logic that you need, something that was difficult to do with jQuery.

Finally, and probably most importantly, it does not require the UI to be completely rewritten as JavaScript. In fact, it seems to be built with with this use case in mind. The tag-line on the site, A modest JavaScript framework for the HTML you already have, is true to it’s word.

So, if you have a web-app with server-side rendering, and you need something a bit more — but not too much more — than what jQuery or native DOM provides, this JavaScript framework might be worth a look.