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.
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.
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
npm install package_name
, and import using a “bare module name”You can also use modules in client-side programming.
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'
andimport { readFile } from 'fs/promises'
work.
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)
});
});
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>`
};
The example source files are available here.