DEV Community

Paige Niedringhaus
Paige Niedringhaus

Posted on • Originally published at paigeniedringhaus.com on

Built-in Module Imports and Exports: JavaScript ES6 Feature Series (Pt 7)

wooden pencil holder full of colored pencils

Introduction

The inspiration behind these pieces is simple: There are still plenty of developers for whom JavaScript doesn’t make a whole lot of sense — or at the very least, is perplexing at times.

According to Wikipedia, JavaScript powers just under 95% of the 10 million most popular web pages, as of May 2017.

Since JS powers so much of the web, I wanted to provide a slew of pieces and examples of ES6+ features that I use regularly, for other developers to reference.

The aim is for these pieces to be short, in-depth explanations of various improvements to the language that I hope will inspire you to write some really cool stuff using JS. Who knows? You might even learn something new along the way.

Piece seven in my series concerns built-in module support in JavaScript, the easier way to make objects, functions, classes, and variables available anywhere in your codebase, with minimal syntax.


Require is replaced by ES modules

Before I dive into built-in modules, let me give you a very brief history lesson on CommonJS and the require syntax.

Pre-ES2015, the most widely used approach to adding various library modules to JavaScript files’ scope was via CommonJS. Node.js uses it, and it’s the system used by most packages on npm today. The main concept in CommonJS modules is a function called require(). When you call this with the module name of a dependency, it makes sure the module is loaded and returns its interface to be used in the file’s scope.

If you’ve ever worked with Node.js scripts, or even older client-side JavaScript files, you’ve likely seen this syntax before. Here’s an example that imports ExpressJS, a widely used server-side framework, into a Node.js app.js file:

Anatomy of CommonJS's require

const express = require("express");
Enter fullscreen mode Exit fullscreen mode

Simply by declaring the variable express and calling require("express"), all of ExpressJS’s various methods are now available to the file.

For a long while, CommonJS modules worked quite well and allowed the JavaScript community, via npm, to share code on a large scale.

But CommonJS has a few quirks that make it a less than ideal solution: namely, the fact that require() is a normal function call taking any kind of argument, not just a string literal, which makes it hard to determine the dependencies of a module without running its code. There are more issues than this, but I won’t get into them here. This is a brief history, after all.

Suffice it to say, ES2015 introduced its own module system, and in doing so, integrated the new module support notation into the language. The main concepts of dependencies and interfaces remain the same, but the details differ — which is what I’ll be discussing now.

NOTE: If you’d like a deeper dive into modules, packages, and CommonJS, I’d recommend reading this chapter out of Eloquent JavaScript.

But for now, let’s get on with ES6’s built-in module support.

Import all the things

One of the biggest departures from CommonJS’s module syntax, when ES modules came to town, is the special new keyword import to access a dependency.

Let’s go through all the different types of imports that are available. In the section below this, I’ll go through the export options as well.

Import default exports

The very first type of import revolves around default imports. This is the most frequently used type of import, and there are a few different ways default exports can be combined with other types of imports.

Simplest example importing only the default export:

import carTires from "./autoParts/tires.js";
Enter fullscreen mode Exit fullscreen mode

In this first example, carTires is the default export from the file tires.js. To get access to it in this file, all that needs to be done is to declare the export by the same name.

If a file has more than one value you’d like access to (like say, a function or variable, plus a default export) the syntax would look like the following example of import of a default export and an additional function:

import carTires, { shineTires } from "./autoParts/tires.js";
Enter fullscreen mode Exit fullscreen mode

The destructured export { shineTires } is what’s known as a named import in this particular import statement, since it’s wrapped in curly braces and exported by the same name from the tires.js file.

There’s one other option where default exports can be combined with an entire module’s contents via a wildcard import *.

Example of importing a default export plus all the other exports in a file:

import carTires, * as tireOptions from "./autoParts/tires.js";
Enter fullscreen mode Exit fullscreen mode

If there was a variable called tireType and a function called rotateTires() from the file tires.js, the importing file could access them both by calling tireOptions.tireType or tireOptions.rotateTires(). Since the additional exports were imported with the syntax * as tireOptions, all exports from the file can be referenced tireOptions.xyz. This is known as a namespace import.

NOTE: If you’re importing a default export along with named or namespace imports, be aware that the default export will have to be declared first.

But I’m getting ahead of myself. Let’s move on from importing default exports to all the other import types, and then the details should become more clear.

An entire module's contents: namespace imports

As I alluded to above, if a file has lots of individual exports you’d like access to, without having to import them all by name, there’s a way to do so with the help of import * as abc or a namespace import.

This inserts abc into the current scope, containing all the exports from the module in the file located in /romanAlphabet/letters.js.

Example of importing multiple exports all at once from a file:

import * as abc from "/romanAlphabet/letters.js";
Enter fullscreen mode Exit fullscreen mode

Here, accessing the exports means using the module name (abc in this case) as a namespace. For example, if the module imported above includes an export singMyAbcs(), you would call it like this: abc.singMyAbcs();

Simple enough, right? Now let’s move on.

A single export from a module: named import

The second example above, in the default export section, touched on is named imports, a simple way to bring in just one object or value into the importing file’s scope using ES6 object destructuring.

Here’s another example of a single named import to make it even more clear:

import { apples } from "./plants/fruits.js";
Enter fullscreen mode Exit fullscreen mode

The object apples is exported from the file fruits.js, either implicitly because the whole module is exported or explicitly using the export statement, and is then inserted into the current scope.

This same syntax can be used for multiple imports too.

Multiple exports from a single module

Similar to a single named import, multiple values and objects from a file can be exported using the same code style.

Example of multiple named imports:

import { carrots, potatoes, onions } from "./plants/veggies.js";
Enter fullscreen mode Exit fullscreen mode

All the exports from veggies.js are imported into the current file’s scope, and are accessible by name, just the same as a single imported value.

Alias an import with a more convenient name

Now suppose you have an export you’d like to import into your file, but it has a terribly long name like mySuperCaliFragilisticExpialidociusObject. That’s quite a pain to type out, right?

Well, there’s a solution: aliasing an export with a more convenient name when importing it. Check this out.

Example renaming a named export for convenience:

import { mySuperCaliFragilisticExpialidociusObject as mySuperObject } from "./maryPoppins.js"
Enter fullscreen mode Exit fullscreen mode

Whenever you need to access the imported value in the current scope, you can simply call it with mySuperObject instead of the original, much longer exported name. How convenient!

Rename multiple exports during import

But say you have multiple long, named export objects. Can you rename all of them on import? Well, as it turns out, you can.

Just like renaming one export, you can rename multiple exports on import.

Example of aliasing multiple exports:

import { ladyMaryCrawley as ladyMary, ladyEdithCrawley as ladyEdith, ladyCoraCrawley as ladyGrantham } from "./downtonAbbeyFamily/ladies.js";
Enter fullscreen mode Exit fullscreen mode

All of the ladies from Downton Abbey being imported into the current file have quite long titles, so, for convenience, the current scope will access them all with their shortened aliases of ladyMary, ladyEdith, and ladyGrantham.

Import side effects only

This next example I’ve never used personally, but you have the option to import an entire module for side effects only, without importing anything. This has the side effect of running the module’s global code, while not actually importing any values.

Example of importing a module without importing anything in particular:

import "./helperFunctions.js";
Enter fullscreen mode Exit fullscreen mode

If anything, this syntax reminds me quite a bit of the way CSS files are now imported into individual JavaScript files.

Dynamic imports

Ok, last import to go over: dynamic imports. This proposal is due to be finalized in the ES2020 release, so I wouldn’t put it into production just yet, but it’s worth being aware of.

The import keyword may be called as a function to dynamically import a module. When used this way, it returns a promise.

Example of dynamic imports with promise syntax:

import ("./waysToTravel.js")
  .then((vehicles) => {
    // do something with planes, trains and automobiles
  });
Enter fullscreen mode Exit fullscreen mode

It can also be used with the newer ES6 async/await syntax.

Example of dynamic imports with async / await syntax:

let vehicles = await import("./waysToTravel.js");
Enter fullscreen mode Exit fullscreen mode

I haven’t used this type of import myself yet, but it’s quite nice that they’re working on so many different ways to access objects and values across scopes with the help of built-in module support.

Export all the other things

All right, we’ve covered the import side of ES6’s module support. Now let’s look at the other half of the equation: the ES6 export() keyword and syntax.

Default exports

If you’ve worked in ReactJS at all, you’ll probably be very familiar with default exports. If you simply place the default keyword after the variable that you’re defining for export, it becomes the default export. Therefore, you’ve excluded it from needing curly braces when being imported into another file. Let’s take a look at some examples.

Examples of a default function, variable or class export from a file:

// this is how to export a function as a default
export default function getMovies() {
  // fetch some movie data and return it
};

// this is how to export a variable as a default
export default const movie = {
 title: "The Lion King", 
 releaseDate: "July 19, 2019",
 synopsis: "Simba idolizes his father, King Mufasa, and takes to heart his own royal destiny on the plains of Africa."
};

// this is how to export an ES6 class (a React class to be exact)
export default class Movies extends Component {
  // render some movie data in JSX
}
Enter fullscreen mode Exit fullscreen mode

Take note: each JavaScript file can only have one default export. All other exports in that same file will just have the export keyword in front of them.

Individual named exports

The next type of export to cover is individual named exports, which I touched on when discussing the imports of such values in the import section above.

Named exports aren’t very complicated — as I said before, anything besides a default export in a file you’d like to make available for use in other JavaScript files can be a named export.

Examples of multiple named exports in a file:

// this first array of pets is available to be imported into any other file because it has the "export" keyword
export const myPets = ["dog", "cat", "guinea pig", "gold fish"];

// mySecretPets can't be directly accessed outside the scope of this file because it lacks the "export" keyword ahead of it
const mySecretPets = ["dragon", "griffin", "Loch Ness monster", "Big Foot"]

// nameMyPets can be called in the scope of other files
export function nameMyPets() {
  console.log(myPets, mySecretPets)
}
Enter fullscreen mode Exit fullscreen mode

As you can see from the examples above, both the variable myPets and the function nameMyPets are explicitly exported from the file and can thus be imported into another file using the named import syntax: import { myPets, nameMyPets } from "./pets.js";. The other variable mySecretPets, however, is not exported from the file and therefore cannot be accessed explicitly outside of its original scope.

Multiple named exports in a single line

There’s another way you can export multiple values from a single file in JavaScript, as well, if you prefer not to type export multiple times throughout the file.

At the end of the module, with syntax almost identical to importing multiple named imports, you can declare any values you’d like to make exportable to other scopes outside of the current file.

Example of a single line with multiple named exports:

export { cakes, cookies, makeDessert, makeTea };
Enter fullscreen mode Exit fullscreen mode

If you can imagine in the example above that the variables cakes and cookies existed along with the functions makeDessert() and makeTea(), and you wanted all of those values to be accessible in other areas of your codebase, simply by exporting them all at the end of the module, you could have access to them wherever else you desired.

It’s a slightly cleaner syntax, but really, explicit exports for each value or a single export for all the values at the end of the module will achieve exactly the same thing for you.

Exporting with aliases

The final export option I’ll cover is exporting with aliases, which is similar to importing with aliases.

Example of aliasing exports:

export { dumplings, xiaoLongBao as soupDumplings, bbqPorkBuns, orderDimSum, pickUpSteamerBaskets, pourTea as fillTeaCups };
Enter fullscreen mode Exit fullscreen mode

Just as with renaming named exports in the import file, you can actually rename the exports as you export them. In the example above, I renamed the variable xiaoLongBao to soupDumplings, and the function pourTea to fillTeaCups for importing into any other scope.

Honestly, I’m not really sure why you’d give a value one name in a file and then choose to export it with a different name to all other files. (If there was a naming collision in another file’s scope, why not just rename the import then?) But it’s an option, and I want to arm you with all the knowledge I can.

And this concludes our tour through the ins and outs of imports and exports via ES6’s built-in module support!


Conclusion

JavaScript’s ES6 syntax has been out for a number of years now (and gaining wider and wider adoption as time passes), but it introduced some groundbreaking changes to the language, which were too foreign for some developers to be excited about learning immediately.

And while I can agree that it’s quite different, it’s also incredibly powerful and makes writing JavaScript code so much easier than it was just a few years prior.

My aim with this series of pieces is to highlight bits of the ES6 syntax you use every day and explain how you can use these new parts of the JavaScript language for maximum impact.

Up until now, there was no standardized, built-in module support for all the wonderfully helpful JavaScript library modules out there in npm. ES2015 changed that and brought in new specialized keywords and a myriad of ways to import and export all sorts of JS values from one scope to another with a lot less fuss and bother than before. It’s been quite a game-changer.

Check back in a few weeks, I’ll be writing about more JavaScript and ES6 or something else related to web development.

If you’d like to make sure you never miss an article I write, sign up for my newsletter here: https://paigeniedringhaus.substack.com

Thanks for reading, I hope you’ll start to take advantage of built-in module support in your own projects if you haven’t already.


References & Further Resources

Top comments (0)