JavaScript Module Exports
Introduction
In modern JavaScript development, code is often organized into modules - separate files that contain related code. This modular approach helps keep code organized, maintainable, and reusable. One of the core concepts of JavaScript modules is the ability to export values from one module and import them into another.
Module exports allow you to specify which parts of your module (functions, objects, values) should be accessible to other modules that import it. This creates cleaner, more modular code where each file has a specific purpose and a clear public interface.
In this tutorial, we'll explore different ways to export values from JavaScript modules and understand when to use each approach.
Basic Exports
Named Exports
The simplest way to export something from a module is using named exports. You can export any declaration (variables, functions, classes) by adding the export
keyword in front of it.
// math.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
export class Calculator {
add(a, b) {
return a + b;
}
}
These exported items can then be imported in another file:
// app.js
import { PI, add, multiply, Calculator } from './math.js';
console.log(PI); // Output: 3.14159
console.log(add(2, 3)); // Output: 5
console.log(multiply(2, 3)); // Output: 6
const calc = new Calculator();
console.log(calc.add(5, 3)); // Output: 8
Export List
You can also define your values first and then export them as a group at the end of your file:
// geometry.js
const PI = 3.14159;
function calculateCircleArea(radius) {
return PI * radius * radius;
}
function calculateCircumference(radius) {
return 2 * PI * radius;
}
// Export list at the end of the file
export { PI, calculateCircleArea, calculateCircumference };
Renaming Exports
Sometimes you might want to export a function or variable under a different name than its original name. You can do this using the as
keyword:
// temperature.js
function convertCelsiusToFahrenheit(celsius) {
return (celsius * 9/5) + 32;
}
function convertFahrenheitToCelsius(fahrenheit) {
return (fahrenheit - 32) * 5/9;
}
export {
convertCelsiusToFahrenheit as toFahrenheit,
convertFahrenheitToCelsius as toCelsius
};
When importing these functions, you'd use the exported names:
import { toFahrenheit, toCelsius } from './temperature.js';
console.log(toFahrenheit(20)); // Output: 68
console.log(toCelsius(68)); // Output: 20
Default Exports
When a module represents a single main thing (like a class or a function), you can use default exports. Each module can have only one default export.
// user.js
export default class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
getInfo() {
return `${this.name} (${this.email})`;
}
}
When importing a default export, you can give it any name you want:
import Person from './user.js'; // Note: no curly braces for default imports
// Person is the User class
const user = new Person('John', '[email protected]');
console.log(user.getInfo()); // Output: John ([email protected])
Named and Default Exports Together
You can mix default and named exports in the same module:
// auth.js
export const AUTH_LEVELS = {
ADMIN: 'admin',
USER: 'user',
GUEST: 'guest'
};
export function validateEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
export default class AuthService {
login(username, password) {
// Login logic
return { success: true, user: username };
}
logout() {
// Logout logic
return { success: true };
}
}
You can import them together as follows:
import AuthService, { AUTH_LEVELS, validateEmail } from './auth.js';
// Use the default export
const auth = new AuthService();
const loginResult = auth.login('user123', 'password');
console.log(loginResult); // Output: { success: true, user: 'user123' }
// Use named exports
console.log(AUTH_LEVELS.ADMIN); // Output: admin
console.log(validateEmail('[email protected]')); // Output: true
Re-exporting
Sometimes you might want to gather exports from various modules into a single module to expose a unified API. This is called re-exporting:
// api/index.js
export { default as User } from './user.js';
export { fetchData, postData } from './http.js';
export { default as Auth } from './auth.js';
export { AUTH_LEVELS } from './auth.js';
Now these can all be imported from the same location:
import { User, fetchData, postData, Auth, AUTH_LEVELS } from './api';
Dynamic Exports
In some cases, you might want to build exports dynamically based on certain conditions:
// config.js
const isProd = process.env.NODE_ENV === 'production';
const devConfig = {
apiUrl: 'http://localhost:3000/api',
debug: true
};
const prodConfig = {
apiUrl: 'https://api.example.com',
debug: false
};
// Export different configurations based on environment
export default isProd ? prodConfig : devConfig;
// Common exports for all environments
export const VERSION = '1.0.0';
export const APP_NAME = 'My App';
CommonJS vs ES Modules
It's important to note the difference between the ES Modules syntax we've been discussing and the older CommonJS format used in Node.js:
ES Modules (Modern)
// Exporting
export const name = 'value';
export default function() { /* ... */ }
// Importing
import { name } from './module.js';
import defaultFunction from './module.js';
CommonJS (Traditional Node.js)
// Exporting
const name = 'value';
function myFunction() { /* ... */ }
module.exports = { name, myFunction };
// OR for a single default export:
module.exports = myFunction;
// Importing
const { name, myFunction } = require('./module.js');
// OR for a single import:
const myFunction = require('./module.js');
While Node.js now supports ES Modules, many existing Node.js projects still use CommonJS. Most modern browser-based projects use ES Modules.
Practical Example: Building a Small Library
Let's create a practical example of a simple utility library with multiple modules:
// utils/string.js
export function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
export function truncate(str, length = 50) {
if (str.length <= length) return str;
return str.slice(0, length) + '...';
}
// utils/array.js
export function unique(array) {
return [...new Set(array)];
}
export function chunk(array, size = 1) {
const chunks = [];
for (let i = 0; i < array.length; i += size) {
chunks.push(array.slice(i, i + size));
}
return chunks;
}
// utils/date.js
export default class DateFormatter {
static formatDate(date, format = 'YYYY-MM-DD') {
// Simple implementation for the example
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return format
.replace('YYYY', year)
.replace('MM', month)
.replace('DD', day);
}
}
export function isToday(date) {
const today = new Date();
const d = new Date(date);
return d.getDate() === today.getDate() &&
d.getMonth() === today.getMonth() &&
d.getFullYear() === today.getFullYear();
}
// utils/index.js
// Re-export everything
export * from './string.js';
export * from './array.js';
export { default as DateFormatter } from './date.js';
export { isToday } from './date.js';
Now you can use your utility library as follows:
import { capitalize, truncate, unique, chunk, DateFormatter, isToday } from './utils';
// String utils
console.log(capitalize('hello world')); // Output: Hello world
console.log(truncate('This is a long text that needs to be truncated', 20)); // Output: This is a long text...
// Array utils
console.log(unique([1, 2, 2, 3, 3, 3])); // Output: [1, 2, 3]
console.log(chunk([1, 2, 3, 4, 5, 6], 2)); // Output: [[1, 2], [3, 4], [5, 6]]
// Date utils
console.log(DateFormatter.formatDate(new Date())); // Output: 2023-05-15 (current date)
console.log(isToday(new Date())); // Output: true
Summary
JavaScript module exports provide a powerful way to share code between files in a clean, organized manner. Here's what we've covered:
- Named exports allow you to export multiple values from a module with their original names
- Default exports are useful when a module primarily exports a single value
- You can use both named and default exports in the same module
- Export renaming gives you flexibility to change export names
- Re-exporting helps you create aggregate modules that combine exports from several modules
- Dynamic exports let you conditionally export different values
Understanding module exports is essential for building maintainable JavaScript applications. It's one of the foundational concepts that enables you to create properly structured code with clear separation of concerns.
Further Resources and Exercises
Resources
Exercises
- Create a module that exports utility functions for working with objects (e.g., deepClone, merge, pick).
- Build a simple calculator module with functions for basic operations and export them as named exports.
- Create a module for a shape library with classes for Circle, Rectangle, and Triangle. Use default export for a main Shape class and named exports for the specific shapes.
- Refactor an existing script to use modules with appropriate exports.
- Create a module that provides different functionality when imported in development vs. production environments.
By mastering module exports, you'll be well on your way to writing cleaner, more maintainable JavaScript code!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)