ponyfoo.com

Mastering Modular JavaScript

Module thinking, principles, design patterns and best practices — Modular JavaScript Book Series
O’Reilly Media164 PagesISBN 978-1-4919-5568-0

Tackle two aspects of JavaScript development: modularity and ES6. With this practical guide, front-end and back-end Node.js developers alike will learn how to scale out JavaScript applications by breaking codebases into smaller modules. We’ll specifically cover features in ES6 from the vantage point of how we can leverage them to improve modularity in our programs. Learn how to manage complexity, how to drive it down, and how to write simpler code by building small modules.

If you’re a frontend developer or backend Node.js developer with a working knowledge of JavaScript, this book is for you. The book is ideal for semi-senior developers, senior developers, technical leaders, and software architects.

This book is part of the Modular JavaScript series.

🗞 Start with the book series launch announcement on Pony Foo
💳 Participate in the crowdfunding campaign on Indiegogo
🌩 Amplify the announcement on social media via Thunderclap
🐤 Share a message on Twitter or within your social circles
👏 Contribute to the source code repository on GitHub
🦄 Read the free HTML version of the book on Pony Foo
📓 Purchase the book from O’Reilly on Amazon

Chapter 5

Modular Patterns and Practices

In this chapter, we’ll take a look at some of the latest language features and how to leverage those in our programs while reducing complexity in the process. We’ll also analyze concrete coding patterns and conventions that can help us develop simple alternatives to otherwise complex problems.

Leveraging Modern JavaScript

When used judiciously, the latest JavaScript features can be of great help in reducing the amount of code whose sole purpose is to work around language limitations. This increases signal—the amount of valuable information that can be extracted from reading a piece of code—while eliminating boilerplate and repetition.

Template Literals

Before ES6, the JavaScript community came up with half a dozen ways of arriving at multiline strings: from strings chained with \ escape characters or + arithmetic operators, to using Array#join, or resorting to the string representation of comments in a function—all merely for multiline support.

Further, inserting variables into a string isn’t possible, but that’s easily circumvented by concatenating them with one or more strings:

'Hello ' + name + ', I\'m Nicolás!'

Template literals arrived in ES6 and resolve multiline strings in a feature that was native to the language, without the need for any clever hacks in user-space.

Unlike strings, with template literals, we can interpolate expressions by using a streamlined syntax. They involve less escaping, too, thanks to using backticks instead of single or double quotation marks, which appear more frequently in English text:

`Hello ${ name }, I'm Nicolás!`

Besides these improvements, template literals also offer the possibility of tagged templates. You can prefix the template with a custom function that transforms the template’s output, enabling use cases like input sanitization, formatting, or anything else.

As an illustrative example, the following function could be used for the sanitization use case mentioned previously. Any expressions interpolated into a template go through the insane function from a library by the same name, which strips out unsafe bits of HTML—tags, attributes, or whole trees—to keep user-provided strings honest:

import insane from 'insane'

function sanitize(template, ...expressions) {
  return template.reduce((accumulator, part, i) => {
    return accumulator + insane(expressions[i - 1]) + part
  })
}

In the following example, we embed a user-provided comment as an interpolated expression in a template literal, and the sanitize tag takes care of the rest:

const comment = 'exploit time! <iframe src="http://evil.corp">
                 </iframe>'
const html = sanitize`<div>${ comment }</div>`
console.log(html)
// <- '<div>exploit time! </div>'

Whenever we need to compose a string by using data, template literals are a terse alternative to string concatenation. When we want to avoid escaping single or double quotes, template literals can help. The same is true when we want to write multiline strings.

In every other case—when there’s no interpolation, escaping, or multiline needs—the choice comes down to a mere matter of style. In the last chapter of Practical Modern JavaScript, “Practical Considerations,” I advocated in favor of using template literals in every case.1 This was for a few reasons, but here are the two most important ones: because of convenience, so that you don’t have to convert a string back and forth between single-quoted string and template literals depending on its contents; and because of consistency, so that you don’t have to stop and think about which kind of quotation mark (single, double, or backtick) to use each time. Template literals may take some time to get accustomed to; we’ve used single-quoted strings for a long time, and template literals have been around only for a while. You or your team might prefer sticking with single-quoted strings, and that’s perfectly fine too.

Note

When it comes to style choices, you’ll rarely face problems if you let your team come to a consensus about the preferred style choice and later enforce that choice by way of a lint tool like ESLint. It’s entirely valid to stick with single-quoted strings and use template literals only when deemed absolutely necessary, if that’s what most of the team prefers.

Using a tool like ESLint and a continuous integration job to enforce its rules means nobody has to perform the time-consuming job of keeping everyone in line with the house style. When tooling enforces style choices, discussions about those choices won’t crop up as often in discussion threads while contributors are collaborating on units of work.

It’s important to differentiate between purely stylistic choices, which tend to devolve into contentious time-sinking discussions, and choices that offer more ground to be covered in the everlasting battle against complexity. While the former may make a codebase subjectively easier to read, or more aesthetically pleasing, it is only through deliberate action that we keep complexity in check. Granted, a consistent style throughout a codebase can help contain complexity, but the exact style is unimportant as long as we enforce it consistently.

Destructuring, Rest, and Spread

The destructuring, rest, and spread features came into effect in ES6. These features accomplish various things, which we’ll now discuss.

Destructuring helps us indicate the fields of an object that we’ll be using to compute the output of a function. In the following example, we destructure a few properties from a ticker variable, and then combine that with a ...details rest pattern containing every property of ticker that we haven’t explicitly named in our destructuring pattern:

const { low, high, ask, ...details } = ticker

When we use destructuring methodically and near the top of our functions, or—even better—in the parameter list, we are making it obvious what the exact contract of our function is in terms of inputs.

Deep destructuring offers the ability to take this one step further, digging as deep as necessary into the structure of the object we’re accessing. Consider the following example, where we destructure the JSON response body with details about an apartment. When we have a destructuring statement like this near the top of a function that’s used to render a view, the aspects of the apartment listing that are needed to render it become self-evident at a glance. In addition, we avoid repetition when accessing property chains like response.contact.name or response.contact.phone:

const {
  title,
  description,
  askingPrice,
  features: {
    area,
    bathrooms,
    bedrooms,
    amenities
  },
  contact: {
    name,
    phone,
    email
  }
} = response

At times, a deeply destructured property name may not make sense outside its context. For instance, we introduce name to our scope, but it’s the name of the contact for the listing, not to be confused with the name of the listing itself. We can clarify this by giving the contact’s name an alias, like contactName or responseContactName:

const {
  title,
  description,
  askingPrice,
  features: {
    area,
    bathrooms,
    bedrooms,
    amenities
  },
  contact: {
    name: responseContactName,
    phone,
    email
  }
} = response

When using : to alias, it can be difficult at first to remember whether the original name or the aliased name comes first. One helpful way to keep it straight is to mentally replace : with the word “as.” That way, name: responseContactName would read as “name as responseContactName.”

We can even have the same property listed twice, if we wanted to destructure some of its contents, while also maintaining access to the object itself. For instance, if we wanted to destructure the contact object’s contents, as we do in the preceding example, but also take a reference to the whole contact object, we can do the following:

const {
  title,
  description,
  askingPrice,
  features: {
    area,
    bathrooms,
    bedrooms,
    amenities
  },
  contact: responseContact,
  contact: {
    name: responseContactName,
    phone,
    email
  }
} = response

Object spread helps us create a shallow copy of an object by using a little native syntax. We can also combine object spread with our own properties, so that we create a copy that also overwrites the values in the original object we’re spreading:

const faxCopy = { ...fax }
const newCopy = { ...fax, date: new Date() }

This allows us to create slightly modified shallow copies of other objects. When dealing with discrete state management, this means we don’t need to resort to Object.assign method calls or utility libraries. While there’s nothing inherently wrong with Object.assign calls, the object spread ... abstraction is easier for us to internalize and mentally map its meaning back to Object.assign without us realizing it, and so the code becomes easier to read because we’re dealing with less unabstracted knowledge.

Another benefit worth pointing out is that Object.assign() can cause accidents: if we forget to pass an empty object literal as the first argument for this use case, we end up mutating the object. With object spread, there is no way to accidentally mutate anything, since the pattern always acts as if an empty object was passed to Object.assign in the first position.

1
You can read a blog post I wrote about why template literals are better than strings at the Pony Foo site. Practical Modern JavaScript (O’Reilly, 2017) is the first book in the Modular JavaScript series. You’re currently reading the second book of that series.
2
Note also that, starting in Node.js v10.0.0, the native fs.promises interface can be used to access promise-based versions of the fs module’s methods.
3
Up until recently, JSON wasn’t, strictly speaking, a proper subset of ECMA-262. A recent proposal has amended the ECMAScript specification to consider bits of JSON that were previously invalid JavaScript to be valid JavaScript.
Unlock with one Tweet!
Grants you full online access to Mastering Modular JavaScript!
You can also read the book on the public git repository, but it won’t be as pretty! 😅