Export Syntax
// Named exports — multiple per file:
export const PI = 3.14159;
export function add(a, b) { return a + b; }
export class Vector { /* ... */ }
// Default export — one per file:
export default class App { /* ... */ }
// Export at end (cleaner):
function helper() {}
const config = {};
export { helper, config };
Import Syntax
// Named imports:
import { add, PI } from "./math.js";
// Rename:
import { add as sum } from "./math.js";
// Import all as namespace:
import * as math from "./math.js";
math.add(2, 3);
// Default import (any name):
import App from "./App.js";
// Combined:
import React, { useState, useEffect } from "react";
// Dynamic import (on demand):
const { add } = await import("./math.js");
// Or with .then():
import("./heavyModule.js").then(mod => mod.init());
// In HTML:
// <script type="module" src="app.js"></script>
Module Patterns
// Barrel file — re-export from folder:
// models/index.js
export { User } from "./user.js";
export { Post } from "./post.js";
// Then import from one place:
import { User, Post } from "./models/index.js";
// Modules are singletons — run once, cached:
// config.js — loads config once, shared everywhere
let config = null;
export async function getConfig() {
if (!config) config = await loadConfig();
return config;
}
// Node.js ESM: add "type": "module" to package.json
// Or use .mjs extension
// CJS: const x = require("./x"); module.exports = x;
⚡ Key Takeaways
- Named exports: explicit, multiple per file, import with exact names
- Default export: one per file, any name when importing
- Dynamic import() loads modules on demand — great for code splitting
- Modules are singletons — executed once, result cached
- Use barrel index.js files to simplify imports from module folders
🎯 Practice Exercises
EXERCISE 1
Refactor a single-file todo app into modules: Todo class, storage.js, ui.js, main.js. Use both named and default exports. Create a barrel index.js.