JavaScript libraries such as jQuery have been the go-to approach for writing JavaScript in the browser for nearly a decade. They’ve been a huge success and necessary intervention for what was once a browser land full of discrepancies and implementation issues. jQuery seamlessly glossed over browser bugs and quirks and made it a no brainer approach to getting things done, such as event handling, Ajax and DOM manipulation.
At the time, jQuery solved all our problems, we include its almighty power and get to work straight away. It was, in a way, a black box that the browser “needed” to function properly.
But the web has evolved, APIs are improving, standards are being implemented, the web is a very fast moving scene and I’m not sure giant libraries have a place in the future for the browser. It’s becoming a module-oriented environment.
Enter the Module
A module is an encapsulated piece of functionality that does one thing only, and that one thing very well. For example, a module may be responsible for adding classes to an element, communicating over HTTP via Ajax, and so on - there are endless possibilities.
A module can come in many shapes and sizes, but the general purpose of them is to be imported into an environment and work out of the box. Generally, each module would have some basic developer documentation and installation process, as well as the environments it’s meant for (such as the browser, server).
These modules then become project dependencies, and the dependencies become easy to manage. The days of dropping in a huge library are slowly fading away, large libraries don’t offer as much flexibility or power. Libraries such as jQuery have recognised this too, which is fantastic - they’ve a tool online which lets you download only the things you need.
Modern APIs are a huge booster for module inspiration, now that browser implementations have drastically improved, we can start to create small utility modules to help us do our most common tasks.
The module era is here, and it’s here to stay.
Inspiration for a First Module
One modern API that I’ve always been interested in since its inception is the classList API. Inspired from libraries such as jQuery, we’ve now got a native way to add classes to an element without a library or utility functions.
The classList API has been around a few years now, but not many developers know about it. This inspired me to go and create a module that utilised the classList API, and for those browsers less fortunate to support it, provide some form of fallback implementation.
Before we dive into the code, let’s look at what jQuery brought to the scene for adding a class to an element:
$(elem).addClass(‘myclass’);
When this manipulation landed natively, we ended up with the aforementioned classList API - a DOMTokenList Object (space separated values) which represents the values stored against an element’s className. The classList API provides us a few methods to interact with this DOMTokenList, all very “jQuery-like”. Here’s an example of how the classList API adds a class, which uses the classList.add()
method:
elem.classList.add(‘myclass’);
What can we learn from this? A library feature making its way into a language is a pretty big deal (or at least inspiring it). This is what is so great about the open web platform, we can all have some insight as to how things progress.
So, what next? We know about modules, and we kind of like the classList API, but unfortunately, not all browsers support it yet. We could write a fallback, though. Sounds like a good idea for a module that uses classList when supported or automatic fallbacks if not.
Creating a First Module: Apollo.js
Around six months ago, I built a standalone and very lightweight module for adding classes to an Element in plain JavaScript - I ended up calling it apollo.js
.
The main goal for the module was to start using the brilliant classList API and break away from needing a library to do a very simple and common task. jQuery wasn’t (and still doesn’t) use the classList API, so I thought it’d be a great way to experiment with the new technology.
We’ll walk through how I made it as well and the thinking behind each piece that makes up the simple module.
Using classList
As we’ve seen already, classList is a very elegant API and “jQuery developer-friendly”, the transition to it is easy. One thing I don’t like about it, however, is the fact we have to keep referring to the classList Object to use one of its methods. I aimed to remove this repetition when I wrote apollo, deciding on the following API design:
apollo.addClass(elem, ‘myclass’);
A good class manipulation module should contain hasClass
, addClass
, removeClass
and toggleClass
methods. All these methods will ride off the “apollo” namespace.
Looking closely at the above “addClass” method, you can see I pass in the element as the first argument. Unlike jQuery, which is a huge custom Object which you’re bound into, this module will accept a DOM element, how it’s fed that element is up to the developer, native methods or a selector module. The second argument is a simple String value, any class name you like.
Let’s walk through the rest of the class manipulation methods that I wanted to create to see what they look like:
apollo.hasClass(elem, ‘myclass’); apollo.addClass(elem, ‘myclass’); apollo.removeClass(elem, ‘myclass’); apollo.toggleClass(elem, ‘myclass’);
So where do we begin? First, we need an Object to add our methods to, and some function closure to house any internal workings/variables/methods. Using an immediate-invoked function expression (IIFE), I wrap an Object named apollo (and some methods containing classList abstractions) to create our module definition.
(function () { var apollo = {}; apollo.hasClass = function (elem, className) { return elem.classList.contains(className); }; apollo.addClass = function (elem, className) { elem.classList.add(className); }; apollo.removeClass = function (elem, className) { elem.classList.remove(className); }; apollo.toggleClass = function (elem, className) { elem.classList.toggle(className); }; window.apollo = apollo; })(); apollo.addClass(document.body, 'test');
Now we’re got classList working, we can think about legacy browser support. The aim for the apollomodule
is to provide a tiny and standalone consistent API implementation for class manipulation, regardless of the browser. This is where simple feature detection comes into play.
The easy way to test feature presence for classList is this:
if ('classList' in document.documentElement) { // you’ve got support }
We’re using the in
operator which evaluates the presence of classList to Boolean. The next step would be to conditionally provide the API to classList supporting users only:
(function () { var apollo = {}; var hasClass, addClass, removeClass, toggleClass; if ('classList' in document.documentElement) { hasClass = function () { return elem.classList.contains(className); } addClass = function (elem, className) { elem.classList.add(className); } removeClass = function (elem, className) { elem.classList.remove(className); } toggleClass = function (elem, className) { elem.classList.toggle(className); } } apollo.hasClass = hasClass; apollo.addClass = addClass; apollo.removeClass = removeClass; apollo.toggleClass = toggleClass; window.apollo = apollo; })();
Legacy support can be done in a few ways, reading the className String and looping through all the names, replace them, add them and so forth. jQuery uses a lot of code for this, utilising long loops and complex structure, I don’t want to completely bloat out this fresh and lightweight module, so set out to use a Regular Expression matching and replaces to achieve the exact same effect with next to no code at all.
Here’s the cleanest implementation I could come up with:
function hasClass (elem, className) { return new RegExp('(^|\\s)' + className + '(\\s|$)').test(elem.className); } function addClass (elem, className) { if (!hasClass(elem, className)) { elem.className += (elem.className ? ' ' : '') + className; } } function removeClass (elem, className) { if (hasClass(elem, className)) { elem.className = elem.className.replace(new RegExp('(^|\\s)*' + className + '(\\s|$)*', 'g'), ''); } } function toggleClass (elem, className) { (hasClass(elem, className) ? removeClass : addClass)(elem, className); }
Let’s integrate them into the module, adding the else
part for non-supporting browsers:
(function () { var apollo = {}; var hasClass, addClass, removeClass, toggleClass; if ('classList' in document.documentElement) { hasClass = function () { return elem.classList.contains(className); }; addClass = function (elem, className) { elem.classList.add(className); }; removeClass = function (elem, className) { elem.classList.remove(className); }; toggleClass = function (elem, className) { elem.classList.toggle(className); }; } else { hasClass = function (elem, className) { return new RegExp('(^|\\s)' + className + '(\\s|$)').test(elem.className); }; addClass = function (elem, className) { if (!hasClass(elem, className)) { elem.className += (elem.className ? ' ' : '') + className; } }; removeClass = function (elem, className) { if (hasClass(elem, className)) { elem.className = elem.className.replace(new RegExp('(^|\\s)*' + className + '(\\s|$)*', 'g'), ''); } }; toggleClass = function (elem, className) { (hasClass(elem, className) ? removeClass : addClass)(elem, className); }; } apollo.hasClass = hasClass; apollo.addClass = addClass; apollo.removeClass = removeClass; apollo.toggleClass = toggleClass; window.apollo = apollo; })();
A working jsFiddle of what we’ve done so far.
Let’s leave it there, the concept has been delivered. The apollo module has a few more features such as adding multiple classes at once, you can check that here, if interested.
So, what have we done? Built an encapsulated piece of functionality dedicated to doing one thing, and one thing well. The module is very simple to read through and understand, and changes can be easily made and validated alongside unit tests. We also have the ability to pull in apollo for projects where we don’t need jQuery and its huge offering, and the tiny apollo module will suffice.
Dependency Management: AMD and CommonJS
The concept of modules isn’t new, we use them all the time. You’re probably aware that JavaScript isn’t just about the browser anymore, it’s running on servers and even TV’s.
What patterns can we adopt when creating and using these new modules? And where can we use them? There are two concepts called “AMD” and “CommonJS”, let’s explore them below.
AMD
Asynchronous Module Definition (usually referred to as AMD) is a JavaScript API for defining modules to be asynchronously loaded, these typically run in the browser as synchronous loading incurs performance costs as well as usability, debugging, and cross-domain access problems. AMD can aid development, keeping JavaScript modules encapsulated in many different files.
AMD uses a function called define
, which defines a module itself and any export Objects. Using AMD, we can also refer to any dependencies to import other modules. A quick example taken from the AMD GitHub project:
define([‘alpha’], function (alpha) { return { verb: function () { return alpha.verb() + 2; } }; });
We might do something like this for apollo if we were to use an AMD approach:
define([‘apollo’], function (alpha) { var apollo = {}; var hasClass, addClass, removeClass, toggleClass; if ('classList' in document.documentElement) { hasClass = function () { return elem.classList.contains(className); }; addClass = function (elem, className) { elem.classList.add(className); }; removeClass = function (elem, className) { elem.classList.remove(className); }; toggleClass = function (elem, className) { elem.classList.toggle(className); }; } else { hasClass = function (elem, className) { return new RegExp('(^|\\s)' + className + '(\\s|$)').test(elem.className); }; addClass = function (elem, className) { if (!hasClass(elem, className)) { elem.className += (elem.className ? ' ' : '') + className; } }; removeClass = function (elem, className) { if (hasClass(elem, className)) { elem.className = elem.className.replace(new RegExp('(^|\\s)*' + className + '(\\s|$)*', 'g'), ''); } }; toggleClass = function (elem, className) { (hasClass(elem, className) ? removeClass : addClass)(elem, className); }; } apollo.hasClass = hasClass; apollo.addClass = addClass; apollo.removeClass = removeClass; apollo.toggleClass = toggleClass; window.apollo = apollo; });
CommonJS
Node.js has been rising for the last few years, as well as dependency management tools and patterns. Node.js utilises something called CommonJS, which uses an “exports” Object to define a module’s contents. A really basic CommonJS implementation might look like this (the idea of “exporting” something to be used elsewhere):
// someModule.js exports.someModule = function () { return "foo"; };
The above code would sit in it’s own file, I’ve named this one someModule.js
. To import it elsewhere and be able to use it, CommonJS specifies that we need to use a function called “require” to fetch individual dependencies:
// do something with `myModule` var myModule = require(‘someModule’);
If you’ve used Grunt/Gulp as well, you’re used to seeing this pattern.
To use this pattern with apollo, we would do the following and reference the exports
Object instead of the window (see last line exports.apollo = apollo
):
(function () { var apollo = {}; var hasClass, addClass, removeClass, toggleClass; if ('classList' in document.documentElement) { hasClass = function () { return elem.classList.contains(className); }; addClass = function (elem, className) { elem.classList.add(className); }; removeClass = function (elem, className) { elem.classList.remove(className); }; toggleClass = function (elem, className) { elem.classList.toggle(className); }; } else { hasClass = function (elem, className) { return new RegExp('(^|\\s)' + className + '(\\s|$)').test(elem.className); }; addClass = function (elem, className) { if (!hasClass(elem, className)) { elem.className += (elem.className ? ' ' : '') + className; } }; removeClass = function (elem, className) { if (hasClass(elem, className)) { elem.className = elem.className.replace(new RegExp('(^|\\s)*' + className + '(\\s|$)*', 'g'), ''); } }; toggleClass = function (elem, className) { (hasClass(elem, className) ? removeClass : addClass)(elem, className); }; } apollo.hasClass = hasClass; apollo.addClass = addClass; apollo.removeClass = removeClass; apollo.toggleClass = toggleClass; exports.apollo = apollo; })();
Universal Module Definition (UMD)
AMD and CommonJS are fantastic approaches, but what if we were to create a module that we wanted to work across all environments: AMD, CommonJS and the browser?
Initially, we did some if
and else
trickery to pass a function to each definition type based on what was available, we’d sniff out for AMD or CommonJS support and use it if it was there. This idea was then adapted and a universal solution began, dubbed “UMD”. It packages this if/else
trickery for us and we just pass in a single function as reference to either module type that was supported, here’s one example from the project’s repository:
(function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['b'], factory); } else { // Browser globals root.amdWeb = factory(root.b); } }(this, function (b) { //use b in some fashion. // Just return a value to define the module export. // This example returns an object, but the module // can return a function as the exported value. return {}; }));
Whoa! Lots happening here. We are passing in a function as the second argument to the IIFE block, which under a local variable name factory
is dynamically assigned as AMD or globally to the browser. Yep, this doesn’t support CommonJS. We can, however, add that support (removing comments this time too):
(function (root, factory) { if (typeof define === 'function' && define.amd) { define(['b'], factory); } else if (typeof exports === 'object') { module.exports = factory; } else { root.amdWeb = factory(root.b); } }(this, function (b) { return {}; }));
The magic line here is module.exports = factory
which assigns our factory to CommonJS.
Let’s wrap apollo in this UMD setup so it can be used in CommonJS environments, AMD and the browser! I’ll include the full apollo script, from the latest version on GitHub, so things will look a little more complex than what I covered above (some new features have been added but weren’t purposely included in the above examples):
/*! apollo.js v1.7.0 | (c) 2014 @toddmotto | https://github.com/toddmotto/apollo */ (function (root, factory) { if (typeof define === 'function' && define.amd) { define(factory); } else if (typeof exports === 'object') { module.exports = factory; } else { root.apollo = factory(); } })(this, function () { 'use strict'; var apollo = {}; var hasClass, addClass, removeClass, toggleClass; var forEach = function (items, fn) { if (Object.prototype.toString.call(items) !== '[object Array]') { items = items.split(' '); } for (var i = 0; i < items.length; i++) { fn(items[i], i); } }; if ('classList' in document.documentElement) { hasClass = function (elem, className) { return elem.classList.contains(className); }; addClass = function (elem, className) { elem.classList.add(className); }; removeClass = function (elem, className) { elem.classList.remove(className); }; toggleClass = function (elem, className) { elem.classList.toggle(className); }; } else { hasClass = function (elem, className) { return new RegExp('(^|\\s)' + className + '(\\s|$)').test(elem.className); }; addClass = function (elem, className) { if (!hasClass(elem, className)) { elem.className += (elem.className ? ' ' : '') + className; } }; removeClass = function (elem, className) { if (hasClass(elem, className)) { elem.className = elem.className.replace(new RegExp('(^|\\s)*' + className + '(\\s|$)*', 'g'), ''); } }; toggleClass = function (elem, className) { (hasClass(elem, className) ? removeClass : addClass)(elem, className); }; } apollo.hasClass = function (elem, className) { return hasClass(elem, className); }; apollo.addClass = function (elem, classes) { forEach(classes, function (className) { addClass(elem, className); }); }; apollo.removeClass = function (elem, classes) { forEach(classes, function (className) { removeClass(elem, className); }); }; apollo.toggleClass = function (elem, classes) { forEach(classes, function (className) { toggleClass(elem, className); }); }; return apollo; });
We’ve created, a packaged our module to work across many environments, this gives us huge flexibility when bringing new dependencies into our work - something a JavaScript library can’t provide us without breaking it into little functional pieces to begin with.
Testing
Typically, our modules are accompanied by unit tests, small bite size tests that make it easy for other developers to join your project and submit pull requests for feature enhancements, it’s a lot less daunting as well than a huge library and working out their build system! Small modules are often rapidly updated whereas larger libraries can take time to implement new features and fix bugs.
Wrapping Up
It was great to create our own module and know we’re supporting many developers across many development environments. This makes developing more maintainable, fun and we understand the tools we’re using a lot better. Modules are accompanied by documentation that we can get up to speed with fairly quickly and integrate into our work. If a module doesn’t suit, we could either find another one or write our own - something we couldn’t do as easily with a large library as a single dependency, we don’t want to tie ourselves into a single solution.
Bonus: ES6 Modules
A nice note to finish on, wasn’t it great to see how JavaScript libraries had influenced native languages with things like class manipulation?A Well, with ES6 (the next generation of the JavaScript language), we’ve struck gold! We have native imports and exports!
Check it out, exporting a module:
/// myModule.js function myModule () { // module content } export myModule;
And importing:
import {myModule} from ‘myModule’;
You can read more on ES6 and the modules specification here.
Comments