Skip to main content

Modules: import / export


Why Modules?

  • Split code into reusable files
  • Avoid global scope pollution
  • Explicit dependencies
  • Each module has its own scope

ES Modules (ESM) — Modern Standard

// math.js — NAMED exports
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }

// Or export at the bottom
const multiply = (a, b) => a * b;
const divide = (a, b) => a / b;
export { multiply, divide };

// main.js — NAMED imports
import { add, subtract } from "./math.js";
import { PI } from "./math.js";
import { multiply as mul } from "./math.js"; // rename

// Import everything as namespace
import * as Math from "./math.js";
Math.add(2, 3); // 5
Math.PI; // 3.14159

Default Export

// Only ONE default export per file

// user.js
export default class User {
constructor(name) { this.name = name; }
greet() { return `Hi, ${this.name}`; }
}

// main.js — any name for default import
import User from "./user.js";
import MyUser from "./user.js"; // same thing, different name

// Mix default and named
import User, { admin } from "./user.js";

Dynamic Imports (Lazy Loading)

// Lazy loading — import only when needed
async function loadModule() {
const module = await import("./heavy-module.js");
module.doSomething();
}

// Conditional loading
if (needsFeature) {
const { Feature } = await import("./feature.js");
new Feature();
}

// import() returns a Promise
import("./module.js")
.then(module => module.init())
.catch(err => console.error(err));

CommonJS (Node.js style, older)

// Exporting (CommonJS)
module.exports = { add, subtract };
// or
exports.add = (a, b) => a + b;

// Importing (CommonJS)
const math = require("./math");
const { add } = require("./math");

Using Modules in HTML

<!-- type="module" enables ES modules -->
<script type="module" src="app.js"></script>
<script type="module">
import { add } from "./math.js";
console.log(add(2, 3));
</script>

<!-- Module scripts are:
- deferred by default
- have their own scope
- strict mode by default
- only loaded once
-->

Re-exporting (Barrel Files)

// index.js — aggregate exports from multiple files
export { User } from "./user.js";
export { Cart } from "./cart.js";
export { default as Product } from "./product.js";
export * from "./utils.js"; // re-export all named exports

// Consumers import from one place
import { User, Cart, Product } from "./index.js";

MCQ — Modules

Q1: How many default exports can a module have?

A) Unlimited B) Two C) One ✅ D) Zero

Answer: C — Only one default export per module, but unlimited named exports.

Q2: How do you rename a named import?

A) import fn as myFn from "module" B) import { fn: myFn } from "module" C) import { fn as myFn } from "module"D) import myFn = fn from "module"

Answer: C

Q3: What does import * as Utils from "./utils.js" do?

A) Imports the default export as Utils B) Imports all named exports as properties of a Utils object ✅ C) Imports everything including private variables D) Dynamically imports the module

Answer: B

Q4: What is dynamic import useful for?

A) Importing variables that change at runtime B) Lazy loading modules only when needed ✅ C) Importing CommonJS modules D) Making imports faster

Answer: B

Q5: Do ES modules run in strict mode by default?

A) No B) Yes ✅

Answer: B

Q6: What is a barrel file?

A) A file that contains only default exports B) An index.js that re-exports from multiple modules for convenience ✅ C) A compressed module bundle D) A module with circular dependencies

Answer: B