A Guide to JavaScript Modules

JavaScript modules allow you to create multiple interconnected scripts that can import and export data and functionality to each other. When connected to each other in this way, connected .js files are known as modules.

There are several benefits to using JavaScript modules:

  • Better code organization
  • Efficiency gains from only loading relevant modules/parts of modules
  • Easy import of others’ code and sharing of your own

The organizational and efficiency gains are greatest for large apps, where, given a large code base, only necessary elements are loaded.

Another great benefit is that you can easily import third-party libraries or parts of them for use in your project.

In this tutorial, we’ll look at how you can do all of this, using a conventional example of a parent and child module, in which the parent imports data and functionality from the child module and a third-party library.

Table of contents

Enabling modules

Setting the HTML script tag

To start using modules, you need to add the type attribute to your script tag and set its value to module:

<!--- Script tag for modules --->
<script type="module" src="parent.js"></script>

Now, when you run the linking HTML file in your browser, it interprets parent.js as a module. This enables the use of import/export syntax.

Running modules

Note that even with this change to the script tag, modules will only run on a live server.

If you are using Visual Studio Code, you can easily start a live server locally without any complicated setup. Just install the Live Server extension and choose to open your HTML document via the live server (right-click > Open with Live Server).

This will start running the linked .js file as a module.

Using modules: import and export

To use import and export requires a second .js file. So in addition to parent.js, create a child.js file in the same directory.

Now, let’s export something from child.js and import it into parent.js.

Exporting

There are two options for exporting: in-line or at the bottom of your script (generally preferable).

Let’s look at some examples of both below, exporting from child.js. We’ll then import what is exported into parent.js.

Exporting in-line

To export, you can use the export keyword in-line before the definition of a function or variable containing data.

To keep the example interesting, we’ll export a function and a result from it that you may use in a real project: a function that generates a unique ID each time it is called:

/* Using the export keyword in-line */
// exporting a function
export function generateID() {
    const time = Date.now();
    const randomNumber = Math.floor(Math.random() * 1000000001);
 
    const uniqueId = time + "_" + randomNumber;
 
    return uniqueId;
} 
// exporting a variable
export const myID = generateID();

Exporting at bottom of module

You can also export functions and data at the bottom of a module. This is generally a cleaner solution, especially if many items are being exported:

/* Exporting at bottom of a module */
function generateID() {
    const time = Date.now();
    const randomNumber = Math.floor(Math.random() * 1000000001);
 
    const uniqueId = time + "_" + randomNumber;
 
    return uniqueId;
} 
const myID = generateID();
export { generateID, myID }

Changing export name

With either solution, you will now be able to import both generateID and myID into another module by specifying that you want to import either one.

You can change the name under which an export item is exported as follows:

/* Changing the name of exports */
export { generateID as makeID, 
   myID as exampleID }

Setting a default export

In some modules, you may want to export only one item, or you want to export one item by default, unless otherwise specified.

In this case, add the keyword default to the item you want to make the default export for that modules:

/* Setting a default export */
export default generateID
export { myID }

Now, when importing from this module, generateID will be imported by default.

Importing

To import something exported by another module, use the import keyword at the top of a module.

Importing regular exports

For an item that is not exported by default, use the following syntax:

/* Importing non-default exports */
import { generateID, myID } from './child.js'
console.log(generateID);
console.log(myID);

If the module you are exporting from has a default export, you do not need to include curly braces.

Importing a default export

You can also specify any valid variable name for the default export and can then use it under that name in your module:

/* Importing a default export */
import IDGenerator from './child.js'
console.log(IDGenerator);

Importing a default and regular export(s)

To import a default export and regular export from a module, use a comma-separated list:

/* Importing a default and non-default export */
import IDGenerator, { myID } from './child.js'
console.log(IDGenerator);
console.log(myID);

Changing import name

You can use a non-default export under a different name in your module as follows:

/* Changing the name of an import */
import IDGenerator, { myID as exampleID } from './child.js'
console.log(IDGenerator);
console.log(exampleID);

Importing third-party libraries

A really cool feature of modules is that you can easily use code that others have written and made available in your project.

A popular third-party library that provides a lot of useful utility functions is lodash (see functions here).

The following code assumes that you have installed lodash in your root project directory from the command line. With npm:

cd "C:\Users\Me\Documents\my-project-directory"
npm i --save lodash

Now, let’s see how you import it.

Example: importing lodash library and functions

By default, lodash exports its entire library of functions. Because the library is a default export, you can name it anything you like when importing it:

/* Importing the full library */
import _ from 'lodash';

You can then use its functions like this:

_.random(0, 10) // Returns a random number between 0 and 10
_.sum([4, 2, 8, 6]); // 20

Alternatively, you can import only the function or functions you will need in your project. You then don’t need to use dot notation to access a function within the library. Instead, you access the functions directly by their import reference name:

/* Importing individual functions */
import { random, sum } from 'lodash';
random(0, 10);
sum([4, 2, 8, 6]);

On the face of it, this is a more efficient solution. However, Alexander Chertkov ran some tests and found little difference performance difference between importing the whole library and individual functions from it.

Given that importing functions individually also requires more maintenance, it is therefore probably preferable in practice to import the entire library.

Summary

Using JavaScript modules syntax, you can call upon data and functionality from other modules. Importantly, modules allow you to load only the code you need for your app. This means that as well as better organization of your code base, modules can lead to performance benefits.

You can also import cool third-party libraries into your project when needed, like lodash.

Related links