How Export Syntax Creates Clear Module APIs
ES6 export syntax offers multiple ways to expose module functionality - inline exports, grouped declarations, and mixed patterns. Choosing the right export style communicates intent and makes your module's public API obvious. Teams using consistent export patterns report better code organization and easier refactoring.
TL;DR
- Export inline with
export function getName() {}
- Group exports with
export { func1, func2, constant }
- Mix exports: combine inline and grouped as needed
- Rename exports with
export { localName as publicName }
const moduleAPI = { getName: () => 'user', func1: true, func2: true }
The Module API Design Challenge
You're creating a utility module that needs to expose various functions, constants, and classes. Some functions are closely related, others are standalone, and you want to provide aliases for certain exports. You need a clear export strategy that makes the module's public API obvious at a glance.
// Inconsistent export patterns
function formatDate(date) {
return date.toLocaleDateString()
}
const API_URL = 'https://api.example.com'
class UserService {
getUser(id) {
return fetch(`${API_URL}/users/${id}`)
}
}
// Mixed export approaches at the end
const exported = { formatDate, API_URL, UserService }
console.log('Inconsistent exports hide API structure')
// module.exports = exported
ES6 export syntax provides consistent patterns that make the module API clear and maintainable:
// Clean, consistent export patterns
function formatDate(date) {
const formatted = date.toLocaleDateString('en-US')
console.log('Formatted date:', formatted)
return formatted
}
const API_URL = 'https://api.example.com'
class UserService {
getUser(id) {
console.log(`Fetching user ${id} from ${API_URL}`)
return fetch(`${API_URL}/users/${id}`)
}
}
console.log('Clean export patterns improve API clarity')
Best Practises
Use inline exports when:
- ✅ Function definition is the main focus
- ✅ Exports are scattered throughout the file
- ✅ Class or function is immediately ready to export
- ✅ Following a declare-and-export pattern
Use grouped exports when:
- 🔄 Organizing multiple related functions
- 🔄 Need to rename exports for public API
- 🔄 Want exports visible at bottom of file
- 🔄 Creating a clear "public interface" section
Avoid when:
- 🚩 Mixing too many export styles creates confusion
- 🚩 Export patterns don't match team conventions
- 🚩 Circular dependencies between modules
- 🚩 Exporting mutable variables or state
System Design Trade-offs
Aspect | Inline Exports | Grouped Exports | Mixed Pattern |
---|---|---|---|
Visibility | Immediate - at definition | Delayed - at end of file | Varied - context dependent |
Refactoring | Easy - change one place | Complex - update two places | Mixed - depends on pattern |
API Overview | Scattered - need to scan file | Clear - all exports together | Partial - some obvious |
Intent | Functional - what it does | Architectural - what's public | Flexible - best of both |
Maintenance | Simple - declaration is export | Careful - keep list updated | Moderate - watch consistency |
Tooling | Good - IDE highlights exports | Excellent - clear export list | Good - mixed support |
More Code Examples
❌ Scattered export chaos
// math-utils.js - Inconsistent and unclear exports
function add(a, b) {
console.log(`Adding: ${a} + ${b}`)
return a + b
}
const PI = 3.14159
// Some exports scattered throughout
function multiply(a, b) {
console.log(`Multiplying: ${a} * ${b}`)
return a * b
}
function subtract(a, b) {
console.log(`Subtracting: ${a} - ${b}`)
return a - b
}
class Calculator {
constructor() {
console.log('Calculator instance created')
this.history = []
}
calculate(operation, a, b) {
let result
switch (operation) {
case 'add':
result = add(a, b)
break
case 'subtract':
result = subtract(a, b)
break
default:
result = 0
}
this.history.push({ operation, a, b, result })
console.log('Calculation result:', result)
return result
}
}
// More exports at the end, completely disconnected
const exportedItems = { add, subtract, PI, Calculator }
// What's exported? You have to scan the entire file!'
console.log('Scattered exports make API unclear')
console.log('Need to hunt through file to find all exports')
✅ Organized export patterns
// math-utils.js - Clean, organized module patterns
// Functions for primary operations
function add(a, b) {
console.log(`Adding: ${a} + ${b} = ${a + b}`)
return a + b
}
function multiply(a, b) {
const result = a * b
console.log(`Multiplying: ${a} * ${b} = ${result}`)
return result
}
function subtract(a, b) {
const result = a - b
console.log(`Subtracting: ${a} - ${b} = ${result}`)
return result
}
function divide(a, b) {
const result = b !== 0 ? a / b : 0
console.log(`Dividing: ${a} / ${b} = ${result}`)
return result
}
// Constants defined inline
const PI = 3.14159265359
const E = 2.71828182846
// Main calculator class
class Calculator {
constructor() {
console.log('Calculator instance created')
this.history = []
}
calculate(operation, a, b) {
let result
switch (operation) {
case 'add':
result = add(a, b)
break
case 'multiply':
result = multiply(a, b)
break
case 'subtract':
result = subtract(a, b)
break
case 'divide':
result = divide(a, b)
break
default:
console.log('Unknown operation:', operation)
result = 0
}
this.history.push({ operation, a, b, result, timestamp: Date.now() })
console.log(`Calculator: ${operation}(${a}, ${b}) = ${result}`)
return result
}
getHistory() {
console.log('Returning calculation history:', this.history.length, 'entries')
return [...this.history]
}
}
Technical Trivia
The Missing Export Catastrophe of 2020: A healthcare app's critical module had functions defined but not exported due to inconsistent export patterns. The sanitizePatientData
function was defined but missing from the export statement, causing patient records to be stored unsanitized. This violated HIPAA compliance and exposed sensitive data.
Why scattered exports failed: The team mixed inline exports with grouped exports inconsistently. When adding new functions, developers forgot to update the export list at the bottom of files. Code reviews focused on functionality, not export completeness.
Consistent export patterns prevent disasters: Adopting either all-inline or all-grouped patterns makes missing exports obvious. Modern bundlers warn about unused functions, and TypeScript catches missing exports during build. ESLint rules can enforce consistent export patterns across projects.
Master Export Syntax: Choose Your Pattern
Decide on inline exports for immediate visibility or grouped exports for API clarity, then stay consistent. Inline exports work well for focused modules, while grouped exports shine for complex utilities. Mix patterns thoughtfully - use inline for primary exports and groups for secondary functions. Consistency across your codebase matters more than the specific pattern chosen.