
Exploring Modules in JavaScript and TypeScript: import & export Explained
Learn how JavaScript and TypeScript use modules to organize code with import/export — covering CommonJS vs ESM, default vs named exports, re-exports, and real project examples.
dogunfx
As your projects grow, splitting code into multiple files becomes essential. Modules make this possible by allowing you to organize logic and share functions, classes, or constants across files.
This post explores how JavaScript and TypeScript handle modules — including import, export, default vs named exports, and how the systems differ in Node.js, Bun, and browsers.
1) What Are Modules?
A module is simply a file that exports something for use in another file.
Without modules, all code shares one global scope (like old <script> tags). Modules isolate logic, making projects easier to maintain.
// greetings.js
export function sayHello(name) {
return `Hello, ${name}!`;
}
// app.js
import { sayHello } from "./greetings.js";
console.log(sayHello("Dogunfx"));
Running this in Node (with ESM) or Bun prints:
Hello, Dogunfx!
2) Named Exports — Exporting Multiple Values
You can export several things from a file.
// math.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
Importing:
// app.js
import { PI, add, multiply } from "./math.js";
console.log(add(2, 3)); // 5
console.log(multiply(3, 4)); // 12
console.log(PI); // 3.14159
Aliasing on Import
import { add as plus } from "./math.js";
console.log(plus(2, 2)); // 4
3) Default Exports — One Main Thing per File
Sometimes you want a file to export one primary value.
// logger.js
export default function log(msg) {
console.log("[LOG]", msg);
}
Importing:
import log from "./logger.js";
log("Modules are cool!");
You can combine default and named exports — though it’s best to keep one style per file for clarity.
export default function main() {}
export const version = "1.0.0";
4) Aggregating Exports (Re-exports)
In larger projects, you might re-export items to simplify imports.
// api/user.js
export function getUser(id) {
return { id, name: "Ada" };
}
// api/post.js
export function getPosts() {
return ["Post 1", "Post 2"];
}
// api/index.js
export * from "./user.js";
export * from "./post.js";
Now consumers can import from one place:
import { getUser, getPosts } from "./api/index.js";
5) Module Types: CommonJS vs ES Modules
CommonJS (CJS)
Used in older Node.js projects.
// old.js
const fs = require("fs");
module.exports = { read: fs.readFileSync };
ES Modules (ESM)
Modern standard for browsers, Node 14+, Bun, Deno, and TypeScript.
// modern.js
import fs from "fs";
export const read = fs.readFileSync;
Switching Between Them (Node.js)
In package.json:
{
"type": "module"
}
Files without extensions must now be .js (ESM) or .cjs (CommonJS).
6) Modules in TypeScript
TypeScript adds types, but the import/export syntax is identical.
// math.ts
export function add(a: number, b: number): number {
return a + b;
}
export default function square(x: number): number {
return x * x;
}
// app.ts
import square, { add } from "./math";
console.log(add(2, 3)); // 5
console.log(square(4)); // 16
Compiler Settings (tsconfig.json)
To enable module features correctly:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src"
}
}
Bun and modern Node handle import syntax directly — no need to compile for simple projects.
7) Importing JSON, CSS, or Other Assets (Modern Runtimes)
Modern runtimes allow non-code imports.
JSON Import (ESM Only)
import config from "./config.json" assert { type: "json" };
console.log(config);
Dynamic Imports (Lazy Loading)
if (process.env.DEBUG) {
const { debug } = await import("./debug.js");
debug("Loaded dynamically!");
}
8) Real-World Example — Organizing a Project
project/
├── src/
│ ├── utils/
│ │ ├── math.js
│ │ └── logger.js
│ ├── api/
│ │ ├── users.js
│ │ ├── posts.js
│ │ └── index.js
│ └── app.js
└── package.json
src/utils/math.js
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;
src/utils/logger.js
export default (msg) => console.log("[App]", msg);
src/api/index.js
export * from "./users.js";
export * from "./posts.js";
src/app.js
import log from "./utils/logger.js";
import { add } from "./utils/math.js";
import { getUser } from "./api/index.js";
log("Starting app...");
console.log("Sum:", add(2, 3));
console.log("User:", getUser("u1"));
Run:
node src/app.js
# or
bun run src/app.js
9) Common Gotchas
| Issue | Cause | Fix |
|---|---|---|
SyntaxError: Cannot use import statement outside a module | Node defaulted to CommonJS | Add "type": "module" to package.json |
Import path missing .js extension | Node ESM requires extensions | Use "./file.js" explicitly |
| Mixed import/export styles | Using CJS + ESM together | Convert to ESM or stick to one style |
| Circular imports | Two modules depend on each other | Restructure or use lazy/dynamic import |
10) Summary
| Concept | Description |
|---|---|
| Named exports | Export multiple items per file |
| Default export | Single main export per file |
| Re-export | Combine multiple modules |
| CommonJS | Legacy Node.js system (require, module.exports) |
| ESM | Modern standard for JS/TS across browsers & runtimes |
References
- MDN — import
- MDN — export
- TypeScript Handbook — Modules
- Node.js Docs — Modules Guide
- Bun Docs — ESM Support
By mastering modules, you’ll gain control over how your project grows — avoiding tangled scripts and embracing structure. Whether in plain JS or TypeScript, import and export are your best tools for writing maintainable, scalable software.
Laravel: The PHP Backbone
How Laravel has become the pillar holding PHP’s relevance over the years, shaping modern web development with an unmatched developer experience.
A Gentle Introduction to Enums, Generics, and Modules in TypeScript
Learn the fundamentals of enums, generics, and modules in TypeScript with simple examples, when-to-use guidance, and common pitfalls—perfect for beginners.