There is a lot of discussion online about organizing JavaScript into modules with lazy-load capabilities and auto-require chains. This level of organization can be very valuable in web applications and even in large scale web sites – but in many websites solutions like Require.js are simply too much and introduce a level of complexity that may be difficult to maintain in the future.
I think it’s important to keep JavaScript organized and maintainable – but it can be achieved in a simpler manner for small-scale sites.
Organization Overview
Most JavaScript on a small-scale site is comprised of 3 things:
- jQuery
- jQuery Plugins
- Supporting JS that uses jQuery and jQuery Plugins.
Each of these has it’s own details and special items of note.
jQuery
For many sites, jQuery is almost a given as it provides some nice behavior wrappers and reduces the complexity of cross-browser support. However, it is fairly big. The conventional wisdom (which I heartily subscribe to) is to load jQuery from a CDN with a local fallback. This local fallback is intended for times when you may be developing without a network connection – not necessarily to protect against a CDN failure.
[gist id=”7563475″]
Some key things to note from the code:
- There is no protocol on the CDN script src attribute – this is so that it works for both HTTP and HTTPS without generating a warning under SSL.
- The local jQuery is only loaded if the CDN jQuery isn’t available.
- We are not including the “latest” jQuery. You should always load a specific version of jQuery, not “latest”. If you simply load “latest”, you’ll automatically get each upgrade and it may break your site.
jQuery Plugins
Plugins are a great way to extend jQuery’s reach but each additional HTTP request for a JS file has an impact on your site’s performance. The best way to handle plugins is to concatenate minified versions of the files into a single plugins.js file. This can be done either manually or via a tool like Grunt leveraging Rigger. I’ll cover this sort of a setup in a future blog post.
Supporting JS (main.js or script.js)
Here is the core of our JavaScript organization. We’ll start with a code snippet, then dissect it.
[gist id=”7564094″]
Wrapped in a Closure
We wrap the code in a closure to protect it from any other variables that may be setup by 3rd party integrations. Also, this lets us pass in external objects (like jQuery) that might be running in noConflict mode.
Components as Objects
We use a simple JavaScript object to model the component. This lets us easily organize the component into logical components with namespaced methods.
Init then Setup
The init function is always called first and is responsible for checking conditions and setting up the component if it is required. Remember, this code will be loaded on every page, but we don’t necessarily want to run the component on each page. The init function acts like a gatekeeper and only runs the setup function if it’s needed on the page
Separate Event Handler from Action
By separating the handler that responds to the click from the action we should take after the click, our code is more extensible. If you realize that you need to call that action as part of the setup, you can easily call the actionForClick function without worrying about any odd behavior stemming from an event handler.
Extend as Necessary
Simply add more elements to the object to extend the functionality. You can even setup some keys pointing to scalars or arrays to track component state.
Init is a Function
We can easily use jQuery’s onDOMReady shortcut and just pass in the init function. Similarly we could use the on body load with a slightly different setup. Be aware though, in the init function “this” isn’t SomeSiteComponent, it’s a context received from jQuery. If you want “this” to be SomeSiteComponent, your init call needs to look like this:
[gist id=”7564237″]
Caching and Compression
As discussed in Improving the Performance of your Website you still definitely want to enable caching and compression of the JavaScript. Since your main.js file may change somewhat frequently, you’ll need to include a cache-busting solution in your setup to facilitate the changes.
In Closing
This is a simple approach to organizing JavaScript into components. I’ve had great success with this approach over the years and it makes it easy to group specific behavior into a single place.