JavaScript ES modules let you split your code into multiple files. Each file (module) can export values (variables, functions, classes, objects) and other files can import them. This improves reusability, maintainability, and keeps your codebase organised.
To use ES modules in the browser you typically write:
<script type="module" src="app.js"></script>
Now app.js can import and export values using the import and export keywords.
import().export const name = value;export function fn() { ... }export default value;import { name } from "./file.js";import defaultName from "./file.js";import * as ns from "./file.js";import "./side-effects.js";Named exports must use their exported names when importing (or be explicitly renamed). A module can have many named exports but only one default export.
Export multiple values from a single file, then import them by their exact names.
// math.js
export const PI = 3.14;
export function add(a, b) {
return a + b;
}
// app.js
import { PI, add } from "./math.js";
console.log(PI);
console.log(add(2, 3));
Each module can have one default export, imported with any name you like.
// greet.js
export default function greet(name) {
console.log("Hello " + name);
}
// app.js
import sayHello from "./greet.js";
sayHello("Alice");
You can combine one default export with additional named exports from the same file.
// utils.js
export default function log(msg) {
console.log("LOG:", msg);
}
export const version = "1.0";
// app.js
import log, { version } from "./utils.js";
log("App started");
console.log(version);
// Import everything as a namespace object
import * as math from "./math.js";
console.log(math.add(2, 3));
// Rename imports using "as"
import { add as sum } from "./math.js";
console.log(sum(4, 5));
// Import for side effects only
import "./polyfills.js";
import() lets you load a module at runtime; it returns a promise that resolves to the module object.
async function load() {
const { formatDate } = await import("./dateUtils.js");
console.log(formatDate(new Date()));
}
load();
Import maps map bare specifiers (like library names) to actual URLs so the browser knows where to load them from.
<script type="importmap">{
"imports": {
"lodash": "https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/lodash.js"
}
}</script>
<script type="module">
import { chunk } from "lodash";
console.log(chunk([1, 2, 3, 4], 2));
</script>
In these examples, most output appears in the browser’s JavaScript console:
console.log(PI); prints 3.14 from the math.js module.add(2, 3) evaluates to 5, showing how functions are shared across files.sayHello("Alice"); logs Hello Alice, using the default export from greet.js.log() function prepends "LOG:" to messages, demonstrating a reusable logging utility.dateUtils.js only when needed, then calls formatDate on a Date object.lodash-es from a CDN and uses its chunk() function.Always open your browser’s DevTools (usually F12) and check the Console tab to see these logs.
math.js, stringUtils.js).type="module" on script tags that use import/export in the browser../module.js or ../folder/module.js.require() with ES import in the same file unless your tooling supports it.greet.js, dateUtils.js).config.js that exports two constants (like API_URL and TIMEOUT) and a default function that logs them. Import all three into app.js.math.js module and experiment with import * as math from "./math.js";. Log the math object to see all its members.import() to dynamically load a module (e.g., stats.js) and then calls a function from it."utils" to your own module file, then import from "utils" inside a <script type="module">.