Skip to main content

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.

javascript
// 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:

javascript
// 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:

javascript
// 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:

javascript
// 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:

javascript
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.

javascript
// 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:

javascript
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:

javascript
// 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:

javascript
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:

javascript
// 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:

javascript
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:

javascript
// 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)

javascript
// Exporting
export const name = 'value';
export default function() { /* ... */ }

// Importing
import { name } from './module.js';
import defaultFunction from './module.js';

CommonJS (Traditional Node.js)

javascript
// 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:

javascript
// 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) + '...';
}
javascript
// 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;
}
javascript
// 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();
}
javascript
// 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:

javascript
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

  1. Create a module that exports utility functions for working with objects (e.g., deepClone, merge, pick).
  2. Build a simple calculator module with functions for basic operations and export them as named exports.
  3. 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.
  4. Refactor an existing script to use modules with appropriate exports.
  5. 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! :)