How I structure JavaScript
Published on
I'm not a big fan of JavaScript frameworks. Most of them seem to assume that all your logic is written in JS, and they don't leave much in the way of support for progressive enhancement, and with variable results for accessible websites.
It's important to make a sidenote about progressive enhancement at this point, it's not just about users which have disabled JavaScript, or who use old browsers, but if you're aiming at really supporting mobile users, mobile connections really do vary in speed and quality, and if you go through a tunnel half way through loading a page, the core content of your webpage should still be usable. Progressive enhancement is still important!
So I write my JavaScript following a few simple principles. First, I think about it in terms of components. This is made much easier in that most of the web sites I work on are very much document based, rather than very rich web apps (although I have seen document-based content delivered using web app frameworks). The HTML delivered to the end users encapsulates as much content as possible, and then my JS modules are designed around the concept of enhancing certain parts of the page.
An example component of this might be responsive images. By default our users get served a small image which means mobile devices get a small initial payload, and everyone gets at least an image. We can then enhance this image by picking a more appropriate size from our image resizing service in a progressive manner for users with more capable machines. Given how big a proportion of our content images are this gives us significant savings without unnecessarily losing quality on larger screens.
A typical JS module has three key methods:
- The constructor, which takes a reference to the DOM node to enhance (also maybe some config)
- An 'init' method which triggers the enhancement (it could do so lazily, so the better resolution image isn't loaded in until the user starts scrolling down)
- A 'destroy' method which removes any event listeners, etc
This structure makes unit testing straight forward (there is some debate about whether the constructor should also init, but I don't like constructors with side effects on the DOM...), and also a fairly straight forward way of using these modules in different contexts.
The document then being delivered then has a controller (for lack of a better word), which performs selections (using querySelector) and the initiates the various modules. We use Require.JS as a module mechanism to encapsulate these classes which works well for us.
The final type of JavaScript we have on a page is often the completely transparent modules that track state that some other modules use to determine their state (e.g., a stats module that might track when you interact with a component. In this case, we use events to communicate between modules, so a stats module just listens to events that a state tracker might fire, and a UI module can also listen to that event and update its state accordingly.
It's not perfect, but it's pretty lightweight, and it's also quite fast. Combined with the cuts the mustard technique, I've found that we can write pretty much pure JS (without even JQuery!) using modern techniques that work cross browser. It's easy for new team members to get up-to-speed with (it's just JavaScript) and it fits with the progressive enhancement model well.