David Heinemeier Hansson (DHH) shares the story of how JavaScript sprinkles in Basecamp evolved into a full-fledged framework called Stimulus. We talked about ins and outs of Basecamp as it is today, Ruby, JavaScript, David’s somewhat new found love for JavaScript, how they open source because they can, and David’s new YouTube series called “On Writing Software Well”.
David Heinemeier Hansson: So one of the things I really wanted with Stimulus was I wanted to solve a couple of specific problems or bad patterns that I was seeing in our sprinkles code at Basecamp. One of the first things I wanted to address was the notion of how do you find the elements that you want to mutate or work with? We had a bunch of different styles.
[24:07] Sometimes we were using a hierarchical approach where you’d say “Oh, give me the parent of the parent of the parent here. I know that the structure of my DOM tree is like this, and I know I want the third parent up.” That’s a pretty brittle way of targeting elements, right? Someone reorganizes things or puts them in a different way and all of a sudden you’re getting the wrong element. So that wasn’t a good pattern.
Another pattern that we’ve used was targeting elements by finding them through CSS classes. So we’d say “Give me the elements that match this CSS class”, which on the one hand seems okay, because a lot of times the CSS classes are explanatory. They say “Oh, this element is about the title of a person, or something”, so when you query for that in the code, it kind of explains what you’re trying to get, but it’s also pretty brittle.
And it’s also not compatible with a lot of the modern CSS styles of writing classes, which are things like BEM, that have a very particular way of writing the classNames, sort of the BEM format, that actually detracts a bit from when you’re trying to use them as code identifiers, but they’re very helpful on the CSS side, right? The designers at Basecamp are really happy with BEM and the advantages that BEM affords them. But it’s brittle. Well, it’s two things - it’s ugly, and then it’s brittle. It’s ugly when you’re trying to target elements that have BEM classes in JavaScript code, because BEM has this format that makes sense for CSS and doesn’t make sense at all for code.
So I didn’t like the ugliness of the code, and I also didn’t like the brittleness of it. But BEM often times – like, you add another –down or a –pad, or whatever it is that you add to a BEM class to ever so slightly tweak it; well, the designers were doing that. Well, again, it broke the code. So that sense of brittleness I wanted to sort of get away from, and that’s where the concept of targets came up.
Basically, Stimulus implores you to only find DOM elements you want to work with through the concept of targets, which is basically just an explicit name that says “This element is going to go by this logical name that belongs to this controller.” And then we can move that name around and it’s not tied to this specific type of element; this could be an input element, it could be a button element, it could be spam element, and as long as it has a data-target that’s of a certain thing, you can always find it… Which not only gives you this sense of clarity around what elements when you read the HTML code are actually used by the dynamic behavior, and in the code itself, it’s very clear when you’re referencing a specific target what that target is and what the purpose is.
It also gives you a sense of generic distance from the specific structure of your DOM tree. As I said, a data target of let’s say hello.name can be applied to an input text, a text area, a span element… So you can write these generic controllers that work with all sorts of different kinds of specific DOM tree expressions, which is really neat… Because that was the other thing I noticed when I was reading through the Basecamp codebase, that we had a lot of feature-specific JavaScript that really was quite generic in its essence, and it was only tied to a specific feature because it was naming certain types of DOM element types, or it was specifically naming certain types of CSS. So you couldn’t really reuse these pieces of feature from one area of the application to another because they were tied to a specific screen and a specific layout.
[27:58] And I wanted to get a bit more generic around that, such that we could build up a library of generic behavior that we could apply to any sort of structure, regardless of how the designer wanted the page to look. So I wanted that distance between those two things.
That’s one of the other reasons why I’m not really a big fan of tying the specific DOM layout to the code, as React does and what a lot of component-style development does, because it doesn’t afford you this sense of reuse that you can develop generic concerns and generic aspects of your dynamic behavior that you can tie to any DOM tree.
That was one of the considerations, so that’s the target concept, that’s a prime motivator, and then the other thing – there’s really only two other things. There’s controllers and there’s actions. And actions are quite similar to targets. They are the triggers. A lot of code we had in Basecamp was using explicit event handling, where we would tie an event handler to usually somewhere up the tree, to some parent, and then we would sort of interrogate that event as it bottled up, to see if it was relevant for the behavior we were trying to match, and then do some code.
Usually, what we were doing - we were using the attribute called data behavior, and then we would scan these data behavior attributes as the events bottled up, and then if the attribute was a match, the data behavior was a match, we would trigger the behavior. Well, that does not provide very readable code always, I’d say. Sometimes we would provide a data behavior on a parent element, and then there would be specific DOM elements inside of that parent that would trigger behavior and we would catch these events and we would do something.
One of the things I really disliked about that was I wanted to get to the point where if someone opened up a piece of the HTML, they could see what was going on, that it didn’t have this shadowland of event handlers living somewhere in some JavaScript files far, far away, so you couldn’t tell what the HTML actually wanted to do. I wanted it to be explicit, such that “Hey, if you click this span, if you click this button, you know what’s going to happen.” That’s the concept of the actions. We make those explicit in the HTML itself; we don’t hide them away as event handlers in the JavaScript code. I mean, underneath there’s an event handler and that’s what Stimulus provides you. It provides the plumbing to do that. But what I’ve found was a lot of JavaScript code was very, let’s say, low-level. Yes, event handling is the way that we process this way and we deal with the UI, but that doesn’t have to be the way we actually write it, and it certainly isn’t the best way to provide understanding of how a system works.
So now you can do data-action on a button or a span or an input or a submit button or anything else that you want when the user clicks that or submits that or hovers on that, or some other sort of event trigger - you can trigger a specific action, and that’s spelled out. So it’s data-action equals, for example, click points to hello@greet. So you read what that button does. You don’t actually need to read the code, you can read “Okay, if a user clicks this button, I’m going to call the greet method on the hello controller.” That’s super explicit, and it means that most of the time I don’t even need to look up what the code actually does, I don’t need to look up the underlying JavaScript controller to understand what actions are available on this view, and I found that that was just so liberating.
[31:52] The second part of that was that just like data targets, the actions were generic and they could be moved around. So if we currently have an action on, let’s say, a button, and we move that action to a link, the controller could remain unchanged, because the controller didn’t actually care or know what type of element invoked its functions, which again is quite different from when you marry the HTML structure with your component and with your dynamic behavior. Then you’re kind of locked in step and you can’t reuse these things and you can’t move them around. So that seemed like a big advantage.
Then finally, the last concern or the last concept is this notion of the controller, which is basically an encapsulation of all the behavior that relates to one aspect of one feature up the system. That’s where we basically just use JavaScript classes, and these classes have methods on them, and these are the methods that we call through the action triggers, and those actions and the methods that they correspond to interact with the targets that we’ve made, and that’s it.
There’s three basic concepts, and it doesn’t take a long time to learn it. Even without learning it, you can read the code, you can read the HTML structure and you can understand what’s going on, and that’s really all we needed. In fact, I was kind of shocked when I first started extracting this stuff; I was kind of thinking “Oh man, there’s gonna be so much stuff here” and it was the real epiphany of seeing that those three basic concepts - the controllers, the targets and the actions - were enough to extract such a wide body of behavior from Basecamp.