Re-Exporting Modules Using Export From in JavaScript

Re-exporting in JavaScript allows a module to import exports from another module and immediately export them without declaring them in the local scope. Using the export ... from syntax, developers can create centralized entry points—often referred to as “barrel files”—to aggregate components, utilities, or API functions. This article covers the fundamental mechanics of the export ... from syntax, its scoping behavior, how it handles named and default exports, and how to deal with name collisions.

What is Re-Exporting?

Re-exporting acts as a direct pass-through mechanism. Instead of manually importing a value and then exporting it across two separate statements, JavaScript provides a combined shorthand:

// Instead of this:
import { getUser } from './user.js';
export { getUser };

// You can write this:
export { getUser } from './user.js';

This pattern simplifies the public API of a library or folder structure by consolidating multiple internal files into a single index.js entry point.

Key Behaviors of export ... from

1. No Local Scope Binding

The most critical behavior of export ... from is that the exported identifiers are not imported into the local module’s scope. They cannot be referenced, modified, or read within the file executing the re-export.

export { calculateTotal } from './billing.js';

// This will throw a ReferenceError:
console.log(calculateTotal); 

If you need to use the value locally in addition to exporting it, you must use standard import and export statements separately.

2. Behavior of export * from

The wildcard re-export syntax exports all named exports from the target module:

export * from './math.js';

Important rules apply to export *: * Default exports are ignored: export * from './module.js' will only re-export named exports. It will not re-export the default export of the target module. * Name collisions are dropped silently: If two modules export an identifier with the exact same name, neither is re-exported via export *. The colliding identifier is omitted from the resulting module to avoid ambiguity, without throwing an immediate build-time error until an importer tries to consume that specific binding.

3. Handling Default Exports

Because default exports require explicit handling, several patterns exist depending on the desired outcome:

4. Namespace Re-Exporting

You can bundle all exports from a module into a single named namespace object using the export * as syntax:

export * as StringUtils from './strings.js';

Consumers importing from this barrel file can then access utilities via the namespace:

import { StringUtils } from './utilities.js';
StringUtils.capitalize('text');

Summary of Syntax Patterns

Pattern Description
export { foo } from './mod.js'; Re-exports a specific named export.
export { foo as bar } from './mod.js'; Renames and re-exports a named export.
export * from './mod.js'; Re-exports all named exports (excludes default).
export * as ns from './mod.js'; Bundles all exports into a single exported namespace object.
export { default } from './mod.js'; Re-exports the default export as the default export.
export { default as Custom } from './mod.js'; Re-exports the default export as a named export.