Logo
Published on

Named Exports

How Named Exports Creates Modular Applications

Named exports let you expose multiple functions, constants, and classes from a single module. This approach creates clean APIs where consumers can import exactly what they need, reducing bundle sizes and improving code organization. Teams using named exports report better maintainability and easier testing.

TL;DR

  • Export multiple functions with export { func1, func2 }
  • Import selectively with import {func1} from './module'
  • Perfect for utility libraries and constant collections
  • Enables tree-shaking to reduce bundle size
export { formatDate, parseJSON }
import { formatDate } from './utils'

The Module Organization Challenge

You're building a utility library that needs to expose multiple functions and constants. Using a single default export would force consumers to import everything, while separate files would create too many imports. You need a clean way to group related functionality while allowing selective imports.

// utils.js - Single file with everything mixed together
function formatDate(date) {
  return date.toLocaleDateString()
}
function formatCurrency(amount) {
  return `$${amount.toFixed(2)}`
}
function validateEmail(email) {
  return email.includes('@')
}
// All functions in one big export
const utils = { formatDate, formatCurrency, validateEmail }
console.log('Mixed utilities:', utils)

Named exports provide clean organization where each function has its own export, enabling selective imports:

// utils.js - Clean named exports
function formatDate(date) {
  const formatted = date.toLocaleDateString()
  console.log('Formatting date:', formatted)
  return formatted
}
function formatCurrency(amount) {
  const currency = `$${amount.toFixed(2)}`
  console.log('Formatting currency:', currency)
  return currency
}
const EMAIL_REGEX = /^[^@]+@[^@]+\.[^@]+$/
// Solution continues...
console.log('Optimized')

Best Practises

Use named exports when:

  • ✅ Creating utility libraries with multiple functions
  • ✅ Exposing constants and configuration values
  • ✅ Building APIs where consumers need selective imports
  • ✅ Organizing related functions in a single module

Avoid when:

  • 🚩 Module has a single primary export (use default export)
  • 🚩 Exporting classes meant to be instantiated once
  • 🚩 Creating circular dependency risks
  • 🚩 Working with CommonJS-only environments

System Design Trade-offs

AspectNamed ExportsSingle Object Export
Tree ShakingExcellent - unused code eliminatedPoor - entire object included
Import ClarityGood - explicit function namesFair - object property access
Bundle SizeSmaller - only imported functionsLarger - complete module
RefactoringEasy - rename exports safelyHard - find all property uses
TestingSimple - test individual exportsComplex - mock entire object
IDE SupportExcellent - autocomplete importsGood - object property hints

More Code Examples

❌ Single object export chaos
// api-client.js - Everything in one big object
function createUser(userData) {
  console.log('Creating user:', userData.name)
  return fetch('/api/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(userData),
  }).then((res) => res.json())
}
function deleteUser(userId) {
  console.log('Deleting user:', userId)
  return fetch(`/api/users/${userId}`, {
    method: 'DELETE',
  }).then((res) => res.json())
}
function getUserProfile(userId) {
  console.log('Fetching user profile:', userId)
  return fetch(`/api/users/${userId}`).then((res) => res.json())
}
const API_BASE = 'https://api.example.com'
const TIMEOUT = 5000
// Single object export - forces importing everything
const apiClient = {
  createUser,
  deleteUser,
  getUserProfile,
  API_BASE,
  TIMEOUT,
}
// In another file: import entire client even for one function
console.log('Importing full API client for just one function')
// import apiClient from './api-client';
// apiClient.createUser(userData); // Only need this one function
✅ Named exports precision
// api-client.js - Clean named exports
const API_BASE = 'https://api.example.com'
const TIMEOUT = 5000
const HTTP_STATUS = {
  OK: 200,
  CREATED: 201,
  NOT_FOUND: 404,
}
async function createUser(userData) {
  console.log('Creating user:', userData.name)
  const response = await fetch(`${API_BASE}/users`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(userData),
  })
  const result = await response.json()
  console.log('User created with ID:', result.id)
  return result
}
async function deleteUser(userId) {
  console.log('Deleting user:', userId)
  const response = await fetch(`${API_BASE}/users/${userId}`, {
    method: 'DELETE',
  })
  console.log('User deleted, status:', response.status)
  return response.status === HTTP_STATUS.OK
}
async function getUserProfile(userId) {
  console.log('Fetching user profile:', userId)
  const response = await fetch(`${API_BASE}/users/${userId}`)
  const profile = await response.json()
  console.log('Profile loaded for:', profile.name)
  return profile
}
// In another file: import only what you need
// import { createUser, API_BASE } from './api-client';
// Tree shaking eliminates unused functions!
console.log('Named exports enable selective imports and tree shaking')

Technical Trivia

The Lodash Bundle Disaster of 2019: A startup's web app had 2MB bundle sizes because they imported the entire Lodash library instead of individual functions. The team was using import _ from 'lodash' everywhere, pulling in 300+ utility functions when they only needed 5.

Why single imports failed: Without named exports, webpack couldn't eliminate unused code. The entire Lodash library shipped to every user, causing 10-second load times on mobile devices. Customer conversion rates dropped 40% before they identified the issue.

Named exports solved everything: Switching to import { debounce, throttle } from 'lodash' or better yet, import debounce from 'lodash/debounce' reduced their bundle by 90%. Modern bundlers can tree-shake unused named exports automatically.


Master Named Exports: When to Choose This Pattern

Use named exports for utility libraries, constants, and any module with multiple related functions. This pattern shines when consumers need selective imports and bundle size matters. Avoid named exports for single-responsibility modules where default exports provide clearer intent. Remember: named exports optimize for flexibility and tree-shaking, while default exports optimize for simplicity.