Skip to content

ES6 - Modules

ES6 introduced a standardized module system for JavaScript, allowing developers to organize code into reusable, self-contained units. Modules help in managing complexity, improving code maintainability, and enabling better dependency management.

A module is essentially a JavaScript file. Variables, functions, or classes defined in a module are private to that module by default. To make them accessible from other modules, you must explicitly export them. To use an exported entity in another module, you import it.

  • export: Used to expose functions, objects, or primitive values from a module so they can be used by other programs/modules.
  • import: Used to bring in exported members from another module.
  • Strict Mode: Modules are implicitly in strict mode.
  • Scope: Each module has its own top-level scope. Variables declared in a module are not global.

There are two main types of exports: named exports and default exports.

A module can export multiple named entities. These can be exported as they are declared or in a single export statement at the end.

// utils.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export class Calculator {
constructor() {}
multiply(a, b) { return a * b; }
}
// Alternatively, export at the end:
// const PI = 3.14159;
// function add(a, b) { ... }
// class Calculator { ... }
// export { PI, add, Calculator };

A module can have at most one default export. This is often used for the primary entity the module provides, like a class or a main function.

logger.js
export default function logMessage(message) {
console.log(`LOG: ${message}`);
}
// Or for a class:
// export default class User { ... }

You can also have both named exports and a default export in the same module.

To use exported members, you use the import statement.

Use curly braces {} to specify which named exports to import.

// main.js
import { PI, add, Calculator } from './utils.js';
console.log(PI);
console.log(add(5, 3));
const calc = new Calculator();
console.log(calc.multiply(4, 5));
// You can also rename imports:
// import { add as sumNumbers } from './utils.js';
// console.log(sumNumbers(10, 2));
// Import all named exports as an object:
// import * as Utils from './utils.js';
// console.log(Utils.PI);
// console.log(Utils.add(1,1));

You can choose any name for the imported default entity.

// main.js (continued)
import myLogger from './logger.js'; // 'myLogger' can be any name
myLogger('Application started.');

3. Importing Both Default and Named Exports

Section titled “3. Importing Both Default and Named Exports”
anotherModule.js
// export const version = '1.0';
// export default function greet() { console.log('Hello!'); }
// main.js
// import greet, { version } from './anotherModule.js';

To use ES modules directly in a web browser, you need to tell the browser that a script is a module using type="module" in the <script> tag.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ES Modules Demo</title>
</head>
<body>
<h1>Check the console for module output!</h1>
<!-- The main.js script will import from utils.js and logger.js -->
<script type="module" src="main.js"></script>
</body>
</html>

Important notes for browser modules:

  • Module scripts are deferred by default (they execute after the HTML is parsed).
  • Paths in import statements are relative to the location of the importing file (e.g., ./utils.js) or absolute URLs.
  • You usually need to serve your HTML file and JavaScript modules via a web server (even a simple local one) due to CORS restrictions when using file:/// URLs for modules.
  • For older browsers that don’t support ES modules, you’d typically use a bundler like Webpack, Rollup, or Parcel, along with a transpiler like Babel, to convert ES modules into a format older browsers can understand.

Node.js has support for ES modules. There are two main ways to enable it:

  1. Use the .mjs file extension for your module files.
  2. Or, in your package.json file, add "type": "module". This tells Node.js to treat all .js files in that project as ES modules.

Example with "type": "module" in package.json:

package.json
{
"name": "my-node-app",
"version": "1.0.0",
"type": "module",
"main": "main.js"
}

Then your utils.js, logger.js, and main.js (as defined earlier) would work. You can run main.js using node main.js.

// main.js (Node.js example)
import fs from 'fs'; // Importing a built-in Node.js module
import _ from 'lodash'; // Importing a third-party package (after npm install lodash)
fs.writeFileSync('test.txt', 'Hello from Node.js module!');
console.log(_.capitalize('hello world'));

ES2020 introduced dynamic imports, which allow you to load modules on demand. The import() function returns a Promise.

async function loadUtility() {
if (someCondition) {
const utilsModule = await import('./utils.js');
utilsModule.add(10, 20);
}
}

This is useful for code splitting, conditional loading, or loading modules whose paths are determined at runtime.

  • Organization: Breaks down large applications into smaller, manageable pieces.
  • Reusability: Code can be easily reused across different parts of an application or in different projects.
  • Encapsulation: Hides internal implementation details of a module, exposing only a public API.
  • Dependency Management: Clearly defines dependencies between different parts of the code.