Modules organize code into reusable pieces. Here's how they work in JavaScript.
ES Modules (ESM)#
1// Named exports
2// math.js
3export const PI = 3.14159;
4
5export function add(a, b) {
6 return a + b;
7}
8
9export function multiply(a, b) {
10 return a * b;
11}
12
13// Named imports
14// app.js
15import { add, multiply, PI } from './math.js';
16
17console.log(add(2, 3)); // 5
18console.log(PI); // 3.14159
19
20// Rename imports
21import { add as sum } from './math.js';
22
23// Import all as namespace
24import * as math from './math.js';
25console.log(math.add(2, 3));
26console.log(math.PI);Default Exports#
1// Default export
2// calculator.js
3export default class Calculator {
4 add(a, b) {
5 return a + b;
6 }
7
8 subtract(a, b) {
9 return a - b;
10 }
11}
12
13// Default import (any name works)
14// app.js
15import Calculator from './calculator.js';
16import MyCalc from './calculator.js'; // Same thing
17
18const calc = new Calculator();
19
20// Default + named exports
21// utils.js
22export default function main() {
23 console.log('Main function');
24}
25
26export function helper() {
27 console.log('Helper function');
28}
29
30// Import both
31import main, { helper } from './utils.js';Re-exporting#
1// Aggregate exports from multiple modules
2// index.js
3export { add, multiply } from './math.js';
4export { default as Calculator } from './calculator.js';
5export * from './helpers.js';
6
7// Rename while re-exporting
8export { add as sum } from './math.js';
9
10// Re-export default
11export { default } from './calculator.js';
12
13// Re-export all with namespace
14export * as math from './math.js';CommonJS (Node.js)#
1// module.exports
2// math.js
3const PI = 3.14159;
4
5function add(a, b) {
6 return a + b;
7}
8
9module.exports = {
10 PI,
11 add,
12};
13
14// Or export directly
15module.exports.subtract = function(a, b) {
16 return a - b;
17};
18
19// require
20// app.js
21const { add, PI } = require('./math.js');
22const math = require('./math.js');
23
24console.log(math.add(2, 3));
25
26// Default-like export
27// calculator.js
28class Calculator {}
29module.exports = Calculator;
30
31// Import
32const Calculator = require('./calculator.js');Dynamic Imports#
1// Dynamic import returns a promise
2async function loadModule() {
3 const math = await import('./math.js');
4 console.log(math.add(2, 3));
5}
6
7// Conditional loading
8async function loadFeature(name) {
9 if (name === 'charts') {
10 const { Chart } = await import('./charts.js');
11 return new Chart();
12 }
13 if (name === 'maps') {
14 const { Map } = await import('./maps.js');
15 return new Map();
16 }
17}
18
19// Lazy loading components (React)
20const LazyComponent = React.lazy(() => import('./HeavyComponent.js'));
21
22// With error handling
23async function safeImport(path) {
24 try {
25 return await import(path);
26 } catch (error) {
27 console.error(`Failed to load module: ${path}`, error);
28 return null;
29 }
30}
31
32// Preloading
33function preloadModule(path) {
34 return import(path);
35}
36
37// Preload on hover
38element.addEventListener('mouseenter', () => {
39 preloadModule('./feature.js');
40});Import Meta#
1// Module metadata
2console.log(import.meta.url);
3// file:///path/to/module.js
4
5// Get directory name (ESM)
6const __dirname = new URL('.', import.meta.url).pathname;
7
8// Environment variables (Vite)
9console.log(import.meta.env.MODE);
10console.log(import.meta.env.VITE_API_URL);
11
12// Hot module replacement (Vite)
13if (import.meta.hot) {
14 import.meta.hot.accept((newModule) => {
15 // Handle update
16 });
17}Module Patterns#
1// Barrel exports (index.js)
2// components/index.js
3export { Button } from './Button.js';
4export { Input } from './Input.js';
5export { Modal } from './Modal.js';
6
7// Import from barrel
8import { Button, Input, Modal } from './components';
9
10// Namespace pattern
11// api/index.js
12import * as users from './users.js';
13import * as posts from './posts.js';
14import * as comments from './comments.js';
15
16export const api = {
17 users,
18 posts,
19 comments,
20};
21
22// Usage
23import { api } from './api';
24api.users.getAll();
25
26// Factory pattern
27// logger.js
28export function createLogger(prefix) {
29 return {
30 log: (msg) => console.log(`[${prefix}] ${msg}`),
31 error: (msg) => console.error(`[${prefix}] ${msg}`),
32 };
33}
34
35// Usage
36import { createLogger } from './logger.js';
37const logger = createLogger('App');Circular Dependencies#
1// Avoid circular dependencies!
2
3// BAD: Circular dependency
4// a.js
5import { b } from './b.js';
6export const a = 'A' + b;
7
8// b.js
9import { a } from './a.js';
10export const b = 'B' + a; // a is undefined!
11
12// SOLUTION 1: Restructure
13// shared.js
14export const shared = 'shared';
15
16// a.js
17import { shared } from './shared.js';
18export const a = 'A' + shared;
19
20// b.js
21import { shared } from './shared.js';
22export const b = 'B' + shared;
23
24// SOLUTION 2: Lazy access
25// a.js
26export function getA() {
27 const { b } = require('./b.js');
28 return 'A' + b;
29}
30
31// SOLUTION 3: Dependency injection
32// a.js
33export function createA(b) {
34 return 'A' + b;
35}Browser vs Node.js#
1<!-- Browser: type="module" -->
2<script type="module">
3 import { add } from './math.js';
4 console.log(add(2, 3));
5</script>
6
7<!-- Or with src -->
8<script type="module" src="./app.js"></script>
9
10<!-- Fallback for older browsers -->
11<script nomodule src="./legacy-bundle.js"></script>1// Node.js: .mjs extension or package.json
2// package.json
3{
4 "type": "module"
5}
6
7// Or use .mjs extension
8// math.mjs
9export function add(a, b) {
10 return a + b;
11}
12
13// Import in Node.js
14import { add } from './math.mjs';
15// or
16import { add } from './math.js'; // with "type": "module"
17
18// Interop: ESM importing CommonJS
19import pkg from './cjs-module.cjs';
20// or
21import { createRequire } from 'module';
22const require = createRequire(import.meta.url);
23const pkg = require('./cjs-module.js');TypeScript Modules#
1// Export types
2export interface User {
3 id: number;
4 name: string;
5}
6
7export type UserRole = 'admin' | 'user' | 'guest';
8
9// Type-only exports
10export type { User, UserRole };
11
12// Type-only imports
13import type { User } from './types';
14
15// Importing JSON (with resolveJsonModule)
16import config from './config.json';
17
18// Declare module augmentation
19declare module 'express' {
20 interface Request {
21 user?: User;
22 }
23}Module Resolution#
1// Relative imports
2import { add } from './math.js';
3import { add } from '../utils/math.js';
4
5// Bare imports (node_modules)
6import React from 'react';
7import { useState } from 'react';
8
9// Absolute imports (with configuration)
10import { add } from '@/utils/math';
11import { Button } from '@components/Button';
12
13// tsconfig.json or jsconfig.json
14{
15 "compilerOptions": {
16 "baseUrl": ".",
17 "paths": {
18 "@/*": ["src/*"],
19 "@components/*": ["src/components/*"]
20 }
21 }
22}
23
24// Vite configuration
25// vite.config.js
26export default {
27 resolve: {
28 alias: {
29 '@': '/src',
30 '@components': '/src/components',
31 },
32 },
33};Tree Shaking#
1// Tree shaking removes unused exports
2
3// math.js
4export function add(a, b) {
5 return a + b;
6}
7
8export function multiply(a, b) {
9 return a * b; // Not imported, will be removed
10}
11
12// app.js
13import { add } from './math.js';
14// multiply is tree-shaken away
15
16// Ensure tree-shakeable code
17// DON'T: Side effects at module level
18let cache = [];
19export function getCache() {
20 return cache;
21}
22
23// DO: Pure exports
24export function createCache() {
25 return [];
26}
27
28// Mark side-effect-free in package.json
29{
30 "sideEffects": false
31}
32// Or specify which files have side effects
33{
34 "sideEffects": ["*.css", "*.scss"]
35}Best Practices#
Organization:
✓ One export per file for large exports
✓ Use barrel files (index.js) for folders
✓ Keep related code together
✓ Avoid deep nesting
Naming:
✓ Use descriptive file names
✓ Match export name to file name
✓ Use camelCase for functions
✓ Use PascalCase for classes/components
Imports:
✓ Group imports logically
✓ External packages first
✓ Then internal modules
✓ Use absolute imports for clarity
Avoid:
✗ Circular dependencies
✗ Side effects in modules
✗ Mixing ESM and CommonJS
✗ Default exports for utilities
Conclusion#
JavaScript modules enable clean code organization and reusability. Use ES modules for modern projects, understand CommonJS for Node.js compatibility, and leverage dynamic imports for code splitting. Keep modules focused, avoid circular dependencies, and structure imports for tree shaking.