Back to Blog
Modules2026-04-20

JavaScript Modules: Import and Export Patterns

A complete tour of named, default, dynamic, and namespace imports.

ES modules replaced CommonJS in browsers and finally in Node. Knowing every form pays off when reading library source.

Named Exports

``js // utils.js export function camelCase(s) { /* ... */ } export const VERSION = '1.0';

// app.js import { camelCase, VERSION } from './utils.js'; `

Named exports are the default choice — explicit, refactor-friendly, tree-shakable.

Default Export

`js export default function Logger() {} import Logger from './logger.js'; `

Use only when a module has one obvious main thing (a class, a config). Avoid for utility collections.

Mixed Exports

`js export default class Logger {} export const LOG_LEVELS = ['debug', 'info', 'warn', 'error']; `

Common in libraries. Consumers can pick either.

Namespace Import

`js import * as fs from 'node:fs'; fs.readFileSync(path); `

Useful when many things from one module are needed; defeats tree-shaking, so avoid for client bundles.

Dynamic Import

`js const { heavyLib } = await import('./heavy.js'); `

Returns a promise. Use for code splitting and conditional loading. Bundlers create separate chunks for each dynamic import.

Re-export

`js export { camelCase, snakeCase } from './case.js'; export * from './validators.js'; `

The barrel pattern. Convenient but can hurt tree-shaking if barrel includes side effects.

import.meta

`js console.log(import.meta.url); // module's own URL `

Use to load module-relative resources without __dirname.

Top-Level Await

`js const config = await fetch('/config').then(r => r.json()); export default config; ``

Allowed at module top level. Pauses module evaluation until the promise settles.

For build-time module optimization see [vite build optimization](/blog/vite-build-optimization).