Have you ever found yourself searching through the 153,882 packages on npm (as of the time of writing), sure that there must be something which solves your problem, but bewildered by your inability to find it? You’re not alone!
The abundance of packages in the JavaScript ecosystem can at times feel more like a curse than a blessing – all the code in the world can’t help you if you don’t know where to find it. However, the great thing about well designed libraries is that you only need a few of them to get you a long way.
With that in mind, here are six of the libraries I use to solve problems in my web and node projects. In alphabetical order:
1. axios
XMLHttpRequest is one of those technologies that I find myself both loving and hating at the same time. Without it, the modern web wouldn’t exist, but the times I’ve been forced to actually use it have made me want to cry.
I’m obviously not the only one – there are a plethora of libraries built to try and make XMLHttpRequest easier. Superagent and request are two popular ones which lack Promise support, while jQuery’s jQuery.ajax
and Angular’s $http service comes close, but with the problem that they’re both only available when you include their monstrous associated frameworks.
Enter axios – the XMLHttpRequest library which takes the good parts from Angular’s $http, and throws out everything else. Here is an example of it’s use:
With axios
axios({
url: 'https://readyourlevel.jamesknelson.com/api/grade',
method: 'post',
headers: {'Content-type': 'text/html; charset=UTF-8'},
data: text
}).then(onGradeResult, onGradeError)
Without axios
let http = new XMLHttpRequest();
http.open("POST", 'https://readyourlevel.jamesknelson.com/api/grade', true);
http.setRequestHeader('Content-type', 'text/html; charset=UTF-8');
http.onreadystatechange = function() {
if (http.readyState == 4) {
if (http.status == 200)
onGradeResult(JSON.parse(http.responseText));
else
onGradeError(http);
}
};
http.onerror = onGradeError;
http.send(text);
2. es5-shim and es6-shim
I’ve lumped these two together, as they do basically the same thing. That is, they make all sorts of ECMAscripty goodness available to you even while hobbled by old-school browsers (without the overhead of an entire build process). No JavaScript developer in their right mind doesn’t want ECMAscripty goodness.
Of course, no amount of shimming is going to bring you to the promised land of arrow functions, destructuring and generators – but Set, Map, WeakSet, Promises, etc. are certainly better than nothing.
That said, there is one function from es6-shim which makes so many tasks so much easier that it justifies the shim’s use all by itself. Here’s an example from my project numbat-ui.
With es6-shim
var targetProps = Object.assign({}, this.managedCallbacks, other, {disabled: disabled});
Without es6-shim
var targetProps = {};
for (key in this.managedCallbacks) {
if (this.managedCallbacks.hasOwnProperty(key)) {
targetProps[key] = this.managedCallbacks[key];
}
}
for (key in other) {
if (other.hasOwnProperty(key)) {
targetProps[key] = other[key];
}
}
targetProps.disabled = disabled;
Check out @kangax‘s amazing ES5 and ES6 compatibility tables for details on what es5-shim and es6-shim support, or if you really want everything ES6 has to offer, consider investing in learning Babel.
3. Highland
ES6 Promises are wonderful and all, but as awesome as Promise.all
is, there are still times when the complexity of your asynchronous code makes you want to pull your hair out.
While use of bluebird and co. can help, they still offer a fundamentally promise-focussed approach, where sometimes what is needed is a completely different abstraction.
One example from my recent work was a node.js web scraper for Aozora Bunko. While the HTTP client I used rightly returns a promise from each HTTP request, looking at the bigger picture shows a flow of data from the original request, through UTF-8 and HTML parsers, through filters, into more HTTP requests, and yet more parsers. What I really needed was to treat my data as a stream.
Highland makes working with streams easy, and cleverly works with your promises instead of asking you to throw away your existing friends and only play with it. Here is some relevant code, with highland manifesting itself as _
:
With Highland
let pipeline = _.pipeline(
_.map((data) => {
// Make Japanese keys from CSV to English to fit RYG's database
let result = {};
KEYS.forEach(([key, newKey], i) => {
if (data[key]) result[newKey] = data[key];
});
result
}),
_.filter((data) => data.characterType == '新字新仮名'),
_.ratelimit(2, 100),
_.flatMap((data) => _(axios({url: data.url, headers: HEADERS}).then(getBookContentHtml)))
);
Without Highland
I’ll leave this as an exercise for the serious reader. Leave a comment if you manage to get it.
4. lodash
For people who’ve come to JavaScript after some experience with batteries-included languages like Ruby or Python, it is almost inevitable that at some point you’ll have the “there is no way that this function isn’t built in” moment. Or if you’re like me, you’ll never stop having them. E.g.
- I wonder what the method to get the last element of an array is? What, it doesn’t exist?!
- Seems I’ve forgotten the method to get the sum of an array’s elements. Nope. I never learned it in the first place because it doesn’t exist.
- Surely javascript has a function to do a deep copy of an object? Nope?! What the fuck were the Netscape gods smoking?!
Lodash fixes this – it is JavaScript’s batteries, so to speak. It provides a huge number of helpful functions and a pleasant-to-use interface.
Here is an example from the Japanese Grader used at Read Your Grade of why you should use lodash:
With lodash
let furiganaSummary = _.chain(kanjiTokens)
.groupBy('key')
.mapValues((tokens) => _.countBy(tokens.map((t) => t.furiganaType)))
.value();
Without lodash
let furiganaSummary = {};
for (let kanjiToken of kanjiTokens) {
let key = kanjiToken.key;
if (furiganaSummary[key])
furiganaSummary[key].push(kanjiToken);
else
furiganaSummary[key] = [kanjiToken];
}
for (let key in furiganaSummary) {
let typeCounts = {};
for (let kanjiToken of furiganaSummary[key]) {
let furiganaType = kanjiToken.furiganaType;
if (typeCounts[furiganaType] === undefined)
typeCounts[furiganaType] = 1;
else
typeCounts[furiganaType] += 1;
}
furiganaSummary[key] = typeCounts;
}
5. Moment.js
Do you find yourself looking up JavaScript’s date-related functions every time you need to touch one? Maybe you long for the simplicity of Rails’ ActiveSupport where 1.day_ago
actually is a Date object. Or worse, maybe you just avoid working with dates at all. After all, surely the user can’t find “Fri Jun 05 2015 13:27:39 GMT+0900 (JST)” that much harder to read than “1 minute ago”, right?
Moment.js fixes this by providing an incredibly simple interface and a number of powerful functions for working with dates, times and time periods. Here is an example from some accounting software I manage:
With moment.js
let dateString = moment().subtract('month', 1).endOf('month').format("YYYY-MM-DD");
Without moment.js
let now = new Date();
let month = ((now.getMonth() + 11) % 12) + 1;
let year = month == 12 ? now.getFullYear() - 1 : now.getFullYear();
let leapYear = thisYear % 4 == 0;
let days = [31, leapYear ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
let paddedMonth = month < 10 ? `0${month}` else month
let parts = `${year}-${paddedMonth}-${days[month - 1]}`;
6. Numeral.js
Almost every project needs to display formatted numbers. Examples include:
- formatting currency with the correct number of decimal places
- truncating long floating point numbers to one or two numbers after the decimal
- padding numbers in dates (like in the above example)
In most languages, you’d have an inbuilt function to help with this – but not in javascript (notice the theme yet?)! So how do you fix it?
Well, you can just format each number manually, but this gets tiring fast. An alternative common solution is to add your own formatting-related helper functions, but this still gets confusing when they start to change between projects, and why spend the time writing your own if you don’t have to?
And you don’t! Numeral.js is a simple library which makes this a breeze, and allows you to learn a single API which you can then transfer to any project which your boss/customer/curiosity presents you with. While it may not seem to be that much effort to do this manually in the short term, the benefits from Numeral.js compound over time. Heres an example from my Read Your Grade project:
With numeral.js
let formattedNumber = numeral(bookScore).format('0.0');
Without numeral.js
let formattedNumber = `${parseInt(bookScope)}.${((bookScore % 1) + '')[2]}`;
Sure, they’re both one line – but which one would you rather read/type?
Honorary Mention: Kefir
Kefir is a Functional Reactive Programming tool which can make wiring up user interfaces fun again. I’ve been using it together with Facebook’s React while building numbat-ui and thoroughly enjoying it. It’s source is lightweight and easy to read, and the API feels well-designed.
So why only an honorary mention? Well, while searching for awesome examples of my usage of Kefir, I couldn’t find any single blocks of code which screamed “wow, this shows just how must time Kefir will save you”. That said, I’m still new to FRP, so this doesn’t mean they’ll never happen. Indeed, my gut feeling is that building UIs with Kefir/React uses such a different thought process to jQuery or Angular that the code which would normally feel painful just doesn’t materialise in the first place.
Kefir/React is something I’d like to write on again in the future once I have more experience (and code) with it. Subscribe to my newsletter to make sure you don’t miss out when I do!
In return for your e-mail, you’ll also immediately receive 2 bonus print-optimised PDF cheatsheets – on ES6 and JavaScript promises.
Have you looked at Reactive Extensions (https://github.com/Reactive-Extensions/RxJS)? If you liked Kefir, I think you might like RxJs too.
Those are some amazing libraries.
I didn’t know about Axios, it seems pretty neat to use it instead of those monster-sized libraries, like jQuery or Angular, at least when you don’t actually need that much.
I knew about Moment.js, but since I’m kind of fresh in web development, I’ve never had the chance to use it. I’ll keep those in mind 🙂
Thanks a lot!
Nice posting!
Do you have any thoughts on using a node compatible fetch library instead of axios?
I didn’t actually know about it when I wrote the post, but it sounds like a great. I’m planning on trying it out myself in my next project.
The Fetch API polyfill seems like the optimal solution: https://github.com/github/fetch (since it’s the standard API meant to replace XHR)
Why not just use fetch instead of axios?
Have you tried using Kefir with Redux instead of React?
There’s also `sugar` @ https://sugarjs.com that’s note-worthy!
I was tinkering with fetch today. The CORS support seems different than axios/jquery/xhr. I was Not able to fetch data from another domain when used in a chrome extension. Anyone have experience with this?