Exporting and Importing Using ES6 Modules

Abstract image used to represent Exporting and Importing Using ES6 Modules
Image by Pawel Czerwiński.

In JavaScript, a module is simply a reusable piece of code that can be imported and used in other parts of your code. This makes it easier to organise your code into smaller, more manageable pieces and allows you to share code between different parts of your application.

Before the introduction of modules in ES6, developers had to rely on various techniques like Immediately Invoked Function Expressions (IIFE), CommonJS, and AMD to achieve modularity. However, with the introduction of modules in ES6, developers can now use a standardised and more convenient way of organising and sharing code.


Exporting Modules

In order to make a module available for use in other parts of your code, you need to export it. In JavaScript, you can export any object, function, or variable by adding the export keyword before it. For example:

// maths.js
export function add(a, b) {
  return a + b;
}


This is a very basic example where we've defined a function called add, and then used the export keyword to make it available for use in other parts of our application. You can also use the export keyword to export multiple objects, functions, or variables from the same module:

// maths.js
export function add(a, b) {
  return a + b;
}

export const pi = 3.1416;

Now our maths.js file has two available exports: the add function, and the constant pi, both exported from the same module.


Importing Modules

Now that we have some exports available in maths.js, it's time to import them and put them to use in another part of the application. We achieve this using the import keyword. For example, importing our add function from the example above would look something like this:

// app.js
import { add } from './maths.js';

console.log(add(2, 3));  //=> 5

Here we've imported add from maths.js using the relative path ./maths.js, then used it to add 2 and 3. We can also import several named exports from that module:

// app.js
import { add, pi } from './maths.js';

console.log(add(2, 3));  //=> 5
console.log(add(2, pi));  //=> 5.1416

Now we've imported both add and pi from maths.js, and used them together.


Default Exports

Whilst you can explicitly name your exports, you can also export a single default value from a module using the export default syntax:

// maths.js
export default function add(a, b) {
  return a + b;
}

In this separate example, export default makes add the default export from maths.js. We can choose a different local name when importing it:

// app.js
import addFunction from './maths.js';

console.log(addFunction(2, 3));  //=> 5

Here, the default export from maths.js is imported as addFunction. There are no curly braces around a default import. A whole maths module would probably offer more than addition, but this keeps the example small.


Mixing Named and Default Exports

You can of course also mix named and default exports in the same module. Here's an example:

// maths.js
export function add(a, b) {
  return a + b;
}

export default function multiply(a, b) {
  return a * b;
}

This time we've exported a named function, add, and a default function, multiply, from maths.js. We can import and use both:

// app.js
import multiplyFunction, { add } from './maths.js';

console.log(multiplyFunction(2, 3));  //=> 6
console.log(add(2, 3));  //=> 5

Issues with Modules


Whilst modules are a powerful and convenient feature of ES6, there are some potential issues you may come across when working with them. Here are a few of the more common ones:

Browser Compatibility

Not all browsers support ES6 modules (yet), so if you want to use them in your application you may need to use a bundler like webpack or Rollup to compile your code into a format that browsers can understand. As with any recent addition in frontend development, it takes time for the browsers to catch up.

Circular Dependencies

ES modules can contain circular dependencies: module A may import from B whilst B imports from A. The risk is when a module reads a binding before it has been initialised, which can throw a ReferenceError. A loader alone does not fix that ordering problem. Prefer clearer dependency boundaries, or arrange for the value to be used only after both modules have initialised.

Naming Conflicts

If you're importing multiple modules that have objects or functions with the same name, you may run into naming conflicts. To avoid this, try to give your objects and functions unique names, or use namespace objects to group related objects and functions together.

You can also rename when importing from a module using the as keyword:

import { pi as piNumber } from './maths.js';

Here we import the pi constant from maths.js but also renamed it to piNumber. This will avoid conflicts if you have another constant called pi in this particular area of your application, but really you would be better off refactoring one or the other to avoid confusion.


The Wrapup

Modules are a powerful and convenient way to organise and share code in JavaScript. By using named exports, default exports, and a combination of both, you can easily create reusable pieces of code that can be imported and used in other parts of your application. Whilst there are some potential issues you may come across when working with modules, these can be easily overcome with a bit of planning and organisation. With modules, you can write cleaner, more modular code that's easier to maintain and share with others.


Want to find out more?

If you need senior handson support with a complex React or Next.js platform, migration, performance issue, or technical SEO problem, send me the context and I'll tell you where I can help.