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