4-4 Modules, import and export

When you build a complicated program, it becomes necessary to break your source code into multiple files called modules for better management and organization. In addition, you’ll often want to take advantage of other’s work and use third-party modules, e.g. install a package from https://npmjs.org. To support these, JavaScript allows programmers to export some functions / variables from a module, and import some functions / variable in another module.

In the JavaScript world (Browsers, Node.js, etc), there are two common ways to organize features from a module.

  • import individual function / variable from a module, as required.
  • import the whole module as an object

Named export and import

Suppose that you want to write a board game in JavaScript, and you’ve found a module dice-util.mjs that provides some useful function for rolling dice. From their documentation, you pick two particular functions named rollDice and rollDiceSum which the module export. You import the two functions in the following ways.

// p131.mjs
import { rollDice, rollDiceSum } from './dice-util.mjs';

let sixDice = rollDice(6, 10);
console.log(`Roll six dice of 10 faces and get ${sixDice}.`)

let diceSum = rollDiceSum(10, 6)
console.log(`Roll 10 dice of 6 faces and their sum is ${diceSum}.`);

In this example, the module name is a (relative) path to a JavaScript module file in the same folder as p131.js. Different platforms (e.g. browsers and Node.js) provide different ways to locate modules. For Node.js, we can use relative or absolute path to specify a file.

How does the author of the module dice-util.mjs indicate which functions to export? One simple way is to attach the keyword export in front of the definition of the functions.

// dice-util.mjs

// internal function, not exported
function randomInteger(min, max) {
  return Math.floor(Math.random()*(max-min+1))+min
}

export function rollDice (count, face) {
  // assume count > 0, face > 1, integers
  let result = [ ];
  for (let n=0; n<count; n++) {
    result.push(randomInteger(1,face));  
  }
  return result;
}

Alternatively, you can specify the functions to export with a statement like export { rollDice, rollDiceSum }.

Named export is only supported by ES Module, a new standard of JavaScript module system that can be used on both client-side (web browsers) and server-side (Node.js). Therefore, existing modules may have limited support for named exports.

Other ways to use modules

We’ve seen some uses of import in previous lectures. For example, in last section, we imported some function from the Node.js built-in fs module.

// p127.mjs
import { readFile, writeFile } from 'fs/promises';

async function copyFile (sourceFileName) {
  let data = await readFile(sourceFileName, 'utf8');
  await writeFile('copy-' + sourceFileName, data, 'utf8');
}
// ...

We can also import from modules in client-side development. For example, in Chapter 2, we used named import and export to load components from a UI library. Before we can import from the module element-plus, we’ve to install it into the current project by npm install. This import statement is handled by the code bundler in the Vite build tool.

// import <el-button> and <el-input> from ElementPlus
import { ElButton, ElInput } from "element-plus";

You can also use named exports from third-party package from NPM. For example, the following example uses two functions from the Math.js package. You need to install the package before running the program. (e.g. with npm install mathjs)

// p132.mjs
import { evaluate, inv } from 'mathjs';

console.log(`sqrt(-1) = ${evaluate("sqrt(-1)")}`);
console.log(`sqrt(i) = ${evaluate("sqrt(i)")}`);

const mat = [ [1,2], [3,4] ];
console.log("A matrix: ", mat)
console.log("and its inverse: ", inv(mat));

As a summary, there are several ways you can use modules in Node.js

  • use built-in modules in Node.js, and import using a “bare module name”
  • install a package from NPM registry using the command npm install package_name, and import using a “bare module name”
  • write the module yourself, and import using a path to the module file

You can also use modules in client-side programming.

  • use a package manager (e.g. npm) to install modules in project folder, and a build tools (e.g. Vite) to bundle all JavaScript code for the client-side of your web app in one (or a few) JavaScript script file. The HTML file will then load this bundle file.
  • modern browsers have native support for ES modules, so it’s possible to use modules without build tools. See the advanced example p331-module-in-browser for a Vue component example. For more information on browser support of modules, refer to online reference.

Default export

The other common way to use modules is to import a module as one module object. This is common in module systems before ES module, e.g. the CommonJS standard in traditional Node.js packages.

We can specify a default export with the default keyword. You can export a function, a class or a variable using export default, and you don’t give it a name. In many cases, you want to group several thing in a module object, as export it as default. This is demonstrated in the following example.

// circle.js

class Circle {
  constructor (x, y, radius) { /* omitted */ }
  area () { /* omitted */ }
  /* more methods */
}

function contains (c1, c2) {
  /* detail omitted */
}

// make a default export module object which contains the class and the function
export default { Circle, contains }

To use this module, import it into a module object as follows. Notice that we don’t use { }, and we can choose an arbitrary name for the module.

// p133.mjs
import cc from './circle.mjs'

let c = new cc.Circle(1,2,10);
let a = c.area();

We’ve also used similar syntax to import a built-in module in Node.js, for example …

// p103.mjs
// show basic info about CPU and memory
import os from 'os';

let cpus = os.cpus();
console.log(`CPU: ${cpus[0].model}, ${cpus.length} core`);
console.log(`Total memory: ${os.totalmem()/1024**3}G`);

and import a module downloaded from https://www.npmjs.com/package/systeminformation .

// p104.mjs
import si from 'systeminformation';

si.cpu()
  .then(data => console.log(data));

Note: some modules support both named export and default export. e.g. both import fs from 'fs/promises' and import { readFile } from 'fs/promises' work.

Load CommonJS modules with import

Before the introduction of the ES module standard, Node.js uses CommonJS modules. Many JavaScript Node.js packages were published in CommonJS format, and existing books and online tutorials often use this traditional JavaScript module format. Commonly, code examples uses statement like let fs = require('fs') to import a CommonJS module. The module is imported as a module object, and saved in the variable fs. You may then access functions using dot notation, e.g. fs.readFile.

In ES module script (extension *.mjs), you cannot use require. Instead, you can import a CommonJS module as a module object in a similar way as in importing default export. For example, you can rewrite let prompts = require('prompts') as import prompts from 'prompts'. The following example uses two packages prompts and figlet from npmjs.org.

// p134.mjs
import prompts from 'prompts';
import figlet from 'figlet';

prompts({
  type: 'text',
  name: 'sentence',
  message: 'Type a short sentence'
}).then( data=> {
  figlet(data.sentence, function(err, data) {
    if (err) throw err;
    console.log(data)
  });
});

Default export from Vue SFC

We also used default export when we write a Vue SFC (Single File Component). In fact, build tool like Vite converts each SFC (*.vue) into a JavaScript module which export the options object of the component.

<template>
The time now is 
<div class='clock'>{{ timeNow }}</div>
</template>

<script>
export default {
  data() {
    return { timeNow: undefined }
  },
  created() { /* omitted */  }
};
</script>

For example, the file MyClock.vue above is translated into a JavaScript module similar to MyClock.mjs below. (The actual process is more complicated, e.g. the template will be compiled into a render function.)

export default {
  data() {
    return { timeNow: undefined }
  },
  created() { /* omitted */  },
  template: `The time now is 
<div class='clock'>{{ timeNow }}</div>`
};

Downloadable examples

The example source files are available here.