Introduction to Javascript modules: import and export

JavaScript modules are now supported in most modern browsers. Let’s start using them!

Browser support includes:

  • Edge 16+
  • Chrome 61+
  • Safari 10.1+
  • iOS Safari 10.3+
  • Chrome for Android 62+

Be sure to check out caniuse for the latest.

Now let’s code.

Setup

We will not be using a module loader, bundler, or third party tool (e.g. no babel, webpack, rollup, etc.) as we are leveraging modern browsers. For this to work, we will need to use type="module" for our scripts.

index.html

<!doctype html>
<html>
<head>
  <meta charset="UTF-8">
  <title>ES6 module demo</title>
</head>
<body>
<script type="module" src="main.js"></script>
</body>
</html>

Our folder structure will be

src
  |_ index.html
  |_ main.js
  |_ module.js

where module.js may be add.js, mathUtils.js, etc., based on the example.

Export

Want to maximize code reuse? JavaScript modules help us achieve this goal. The export statement allows us to write modules that can export objects, functions, or primitive values, which can then be utilized by other programs using the import statement.

There are two ways to export: default and named. Let’s write a quick utility and then show how to utilize default and named exports.

add.js

let add = (a,b) => a + b;

Default export

The default export will be exported by, yep you guessed it, default. This means that it will be the base import used by another piece of code. Let’s see how that works.

add.js

let add = (a,b) => a + b;
//export my function for later use (i.e. import)
export default add;

main.js

import add from './add.js';
console.log(add(5,4));  //9

As the add function is the default export, even if we change the name of the identifier when importing, it will still map to the add function.

//changing the identifier to show how it does not matter
import foo from './add.js';
console.log(foo(5,4));  //9

We can also export our function without giving it a name.

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

We can still use it in our code the same way as before

import add from './add.js';
console.log(add(5,4));  //9

Named export

Named export allows us to export several values. When using import, we must use the same name (i.e. identifier) for the corresponding item.

multiply.js

const multiply = (x,y) => x * y;
export {multiply};

main.js

import {multiply} from './multiply.js';
console.log(multiply(10,10));  //100

Now, what happens if we try to change the name of our function when importing? Recall that when using default exports, we could change our local identifier and still obtain our default. What happens with named exports?

multiply.js

const multiply = (x,y) => x * y;
export {multiply};

main.js

import foo from './multiply.js';
console.log(foo(10,10));  // SyntaxError: Importing binding name 'default' cannot be resolved by star export entries.

Doing this will cause an error.

With named exports, we can import multiple items as once

mathUtils.js

function add(x,y) {
  return x + y;
}

let multiply = (x,y) => x * y;

export {add, multiply};

main.js

import {add, multiply} from './mathUtils.js';
console.log(add(5,4));  //9
console.log(multiply(10,10));  //100

Import

The import statement allows us to import code which is exported by a module.

For the following examples, we will utilize the module below.

mathUtils.js

function add(a,b) {
  return a + b;
}

function subtract(a,b) {
  return a - b;
}

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

function divide(a,b) {
  return a / b;
}

function logAnswer(answer) {
  console.log(answer);
}

export default logAnswer;
export {add,subtract,multiply,divide};

Import a single export from a module

import {add} from './mathUtils.js';
console.log(add(5,4));  //9

This will insert add into the current scope.

Import multiple exports from a module

import {add, subtract} from './mathUtils.js';
console.log(add(5,4));        //9
console.log(subtract(5,4));   //1

This will insert both add and subtract into the current scope.

Import all contents from a module

import * as myMathUtils from './mathUtils.js';
console.log(myMathUtils.multiply(5,5));  //25

This will insert myMathUtils into the current scope and allow us to access all the exports from the module. Note: to use the exports we must utilize dot notation (e.g. myModule.doSomething());

Import default export

import log from './mathUtils.js';
log('Hello Module'); //Hello Module

A note on default import
Default exports do not require brackets (i.e. {}) when importing.

import add from './add.js';
console.log(add(5,4));  //9

Conclusion

JavaScript modules can help us achieve key programming best practices including code reuse, separation of concerns, encapsulation, and more. As modern browsers now support JavaScript modules through the addition of type="module" to our scripts, we can leverage import and export to build ES6 modules today.

Additional Resources

Updated: