Pascal Laliberté has written a short, free, web-based book which advocates a modest and layered approach to using JavaScript.
I make the case for The JS Gradient, a principle whereby your app can have multiple coexisting modern JS approaches, starting from the global sprinkles to spot view-models to, yes, an SPA if that’s really necessary. At each point in the gradient, you’ll see when it’s a good idea to go a step further toward heavier JavaScript, or not.
Pascal’s philosophy starts with the following ideals:
- prefer server-generated HTML over JavaScript-generated HTML. If we need to add more complex JavaScript layers we may deviate from that ideal, but this should be the starting point;
- we should be able to swap and replace the HTML on a page on a whim. We can then support techniques like pjax (replacing the whole body of a page with new HTML such as with Turbolinks) and ahah (asynchronous HTML over HTTP: replacing parts of a page with new HTML, so as to make our app feel really fast while still favouring server-generated HTML;
- favour native Browser APIs over proprietary libraries. Use the tools the browser gives us (History API, Custom Event handlers, native form elements, CSS and the cascade) and polyfill older browsers.
He argues that a single application can combine the options along the JS Gradient, but also that we need only move to a new level if and when we reach the current level’s threshold.
He defines the levels as follows:
- Global Sprinkles: general app-level enhancements that occur on most pages, achieved by adding event listeners at
document
level to catch user interactions and respond with small updates. Such updates might include dropdowns, fetching and inserting HTML fragments, and Ajax form submission. This might be achieved via a single, DIY script (or something like Trimmings) that is available globally and provides reusable utilities viadata-
attributes; - Component Sprinkles: specific page component behaviour defined in individual
.js
files, where event listeners are still ideally set on thedocument
; - Stimulus components: where each component’s HTML holds its state and defines its behaviour, with a companion controller
.js
file which wires up event handlers to elements; - Spot View-Models: using a framework such as Vue or React only in specific spots, for situations where our needs are more complex and generating the HTML on the server would be impractical. Rather than taking over the whole page, this just augments a specific page section with a data-reactive view-model.
- A single-page application (SPA): typically an all-JavaScript affair, where whole pages are handled by Reactive View-Models like Vue and React and the browser’s handling of clicks and the back button are overriden to serve different JavaScript-generated views to the user. This is the least modest approach but there are times when it is necessary.
One point to which Pascal regularly returns is that it’s better to add event listeners to the document
(with a check to ensure the event occurred on the relevant element) rather than to the element itself. I already knew that Event Delegation is better for browser performance however Pascal’s point is that in the context of wanting to support swapping and replacing HTML on a whim, if event listeners are directly on the element but that element is replaced (or a duplicate added) then we would need to keep adding more event listeners. By contrast, this is not necessary when the event listener is added to the document
.
Note: Stimulus applies event handlers to elements rather than the document
, however one of its USPs is that it’s set up so that as elements appear or disappear from the DOM, event handlers are automatically added and removed. This lets you swap and replace HTML as you need without having to manually define and redefine event handlers. He calls this Automated Behaviour Orchestration and notes that while adding event listeners to the document
is the ideal approach, the Stimulus approach is the next best thing.
Also of particular interest to me was his Stimulus-based Shopping Cart page demo where he employs some nice techniques including:
- multiple controllers within the same block of HTML;
- multiple Stimulus actions on a single element;
- controller action methods which use
document.dispatchEvent
to dispatch Custom Events as a means of communicating changes up to other components; - an element with an action which listens for the above custom event occurring on the
document
(as opposed to an event on the element itself).
I’ve written about Stimulus before and noted a few potential cons when considering complex interfaces, however Pascal’s demo has opened my eyes to additional possibilities.