๐Ÿงฉ Day 25 — Modules & Import/Export

๐Ÿงฉ Day 25 — Modules & Import/Export

Hey Guys, Modular code is maintainable code. Today we’ll cover ES modules, named vs default exports, dynamic imports, tree-shaking, and patterns for organizing scalable front-end projects.


๐Ÿ“ฆ Why Modules?

Modules let you split code into logical files with clear boundaries. This makes development, testing, and maintenance easier. Modern bundlers optimize modules (tree-shaking) and browsers support ESM natively.

๐Ÿงพ Named exports vs Default export

Named exports let you export multiple bindings; default export is for a single primary value.

// math.js
export function add(a,b){ return a+b }
export function mul(a,b){ return a*b }

// main.js
import { add, mul } from './math.js';

// default
// logger.js
export default function log(msg){ console.log(msg); }

// main2.js
import log from './logger.js';
  

๐Ÿ”— Re-exporting and index pattern

Create an `index.js` to gather exports and simplify imports:

// components/index.js
export { default as Button } from './Button.js';
export { default as Modal } from './Modal.js';

// usage
import { Button, Modal } from './components';
  

⚡ Dynamic imports (code-splitting)

Load modules on demand with `import()` — great for reducing initial bundle size and lazy-loading heavy features.

document.getElementById('load').addEventListener('click', async () => {
  const { heavy } = await import('./heavy.js');
  heavy.run();
});
  

๐ŸŒณ Tree-shaking & side effects

Bundlers remove unused exports (tree-shaking) only when modules are side-effect free. Avoid top-level code that has side effects if you want tree-shaking to work effectively.

๐Ÿ—‚ Organizing modules in projects

Use feature folders (each feature contains its components, styles, tests) or layer-based structure (components, services, utils). Keep APIs small and focused.

๐Ÿ” Module boundaries & encapsulation

Expose only what others need. Keep internal helpers private (not exported) and test them indirectly via the public API.

๐Ÿ›  Interop: CommonJS vs ESM

Node historically used CommonJS (`require/module.exports`). Many modern tools support ESM. Use transpilers/bundlers (Babel/Webpack/Vite) when mixing environments.

// CommonJS
const lib = require('./lib');

// ESM
import lib from './lib.js';
  

๐Ÿ” Practical example: small module service

// apiService.js
const BASE = '/api';

export async function get(url) {
  const r = await fetch(BASE + url);
  if (!r.ok) throw new Error('Network');
  return r.json();
}

export async function post(url, body) {
  const r = await fetch(BASE + url, { method:'POST', body: JSON.stringify(body), headers:{'Content-Type':'application/json'}});
  return r.json();
}
  

๐Ÿ“ Best practices

  • Keep modules small and focused (single responsibility).
  • Prefer named exports for utilities (makes tree-shaking easier).
  • Use index files to provide clean public APIs.
  • Avoid circular dependencies — they cause weird runtime bugs.

๐Ÿงช Testing modules

Import modules in your tests and mock external dependencies. With modular code, tests become easier and faster.

๐Ÿ“ Practice tasks

  1. Refactor a monolithic script into modules and set up an `index.js` to export a public API.
  2. Implement lazy-load for a heavy charting component using `import()`.
  3. Configure a small Vite project to support ESM and code-splitting.

๐ŸŽฏ Summary

Modules are the foundation of maintainable JavaScript apps. Learn named/default exports, index re-exports, dynamic imports, and organize features into folders. This pays off as your project grows.

Comments