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.