Sample Video Frame

31: Modules and import

Originally JavaScript had no concept of "modules" because it lived entirely in the browser. A systems language needs to have modules, so Node.JS added modules through the CommonJS system. This uses the require function to load modules, which you've been using this whole time.

In Exercise 13 you wrote a simple module named geometry.js:

const area = (r) => Math.PI * r ** 2;
const circumference = (r) => 2 * Math.PI * r;

module.exports = {
    area: area,
    circumference: circumference

This code exposed the area and circumference functions using the module.exports "magic" variable.

You then used require("./geometry") to access it:

const geometry = require('./geometry');

let area51 = geometry.area(2.8);
let circ2 = geometry.circumference(6);

console.log(`Area: ${area51}, Circumference: ${circ2}`);

This works as the default system in Node.JS, and during the course I stuck to the default to keep things simple. You now need to learn about the "module" version of...modules.

Using import and .mjs Files

The new style of modules (confusingly called...modules) use a new syntax with the import keyword rather than functions and special variables. Let's start by rewriting the Exercise 13 code:

import geometry from "./geometry.mjs";

let area51 = geometry.area(2.8);
let circ2 = geometry.circumference(6);

console.log(`Area: ${area51}, Circumference: ${circ2}`);

You can see that the code is almost exactly the same except the first line is import geometry from "./geometry.mjs";. Wait, why does it end in .mjs? That's how you tell Node.JS that this code is a module.

That means, you can't just change the code.js file, but you also have to rename it to code.mjs so Node.JS will run it. If you don't then node will give you this error:

import geometry from "./geometry.mjs";

SyntaxError: Cannot use import statement outside a module

Rename this file to code.mjs so you can run it, then rename the geometry.js file to geometry.mjs before making these changes:

export const area = (r) => Math.PI * r ** 2;
export const circumference = (r) => 2 * Math.PI * r;

export default {

The changes in this file are:

  1. I change the const area and const circumference lines to be export const area and export const circumference. This exports those individually.
  2. module.exports is replaced with export default.
  3. I use the short-hand syntax for the { } used with export default rather than repeating area: area. This isn't a special thing just for export default but a common help for making { } data objects. If the key is the same as a variable then you can put just the variable in the object and it'll create the key:key for you.
  4. When you add something to the export default it becomes available when someone uses import geometry from "geometry.mjs";.

Once you do all that you get almost the same setup as with the CommonJS style.

Named Exports

If you want only one function out of this module you can do this:

import { area } from "./geometry.mjs";

This will import only area from the module, and make that name directly available so you can write code like this:

let area51 = area(2.8);

The only problem is you have to use export const on each function (or variable) you want to allow this kind of import.

You can also rename these imports:

import { area as BigArea } from "./geometry.mjs";

After you do this BigArea is the name for that function. This is useful when functions you import conflict with common variable names like path.

Modules in package.json

It is annoying to have to name every file with .mjs just to keep Node.JS happy, so you can add the following to your package.json to tell node that all .js files are modules:

  "type": "module",

After you add this, node will treat all .js files as modules, so you can stop renaming them to .mjs.

Then .cjs Files, Sigh

Now that you have package.json telling node everything is a module you then have to name some files with .cjs so they'll run correctly. For example, Knex is a database library that uses the old CommonJS system and it expects its configuration file to be a CommonJS module. That means you have to name its knexfile.js to knexfile.cjs before it will work.

If you run into any JavaScript tool that's relatively old and it has a configuration .js file then chances are you'll have to name it with .cjs to make it work.

Dynamic Imports with import()

If you ever need to load a module based on a string you'll run into trouble with the new import syntax. The standard requires that import has to be at the top of the file, so you can't do something like this:

if(load_another_module) {
  import other_module from "other_module";

This will fail, so what can you do? The require() function had the advantage that you could use it anywhere, and the import() function does the same thing:

if(load_another_module) {
  const other_module = await import("other_module");

This is new enough that some IDEs and "linters" will claim that it's not allowed, even though import() is completely valid.

The Full Study

Mozilla's MDN has the most complete documentation on import and it also covers the import() function.

Mozilla MDN also has documentation for export that you should read in combination with the import documentation.

What you should do next is study the documentation and try to use it in some code. Try with this geometry.mjs code until you know the different ways to use export and import, then try using it after creating a package.json with "type":"module". In the video I'll demonstrate this, but try it yourself first.

Learn JavaScript Today

Register today for the Early Access course and get the all currently available videos and lessons, plus all future modules for no extra charge.

Still Not Sure? Check out more curriculum.