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:
Re-export a default export as default:
export { default } from './Button.js';Re-export a default export as a named export:
export { default as Button } from './Button.js';Re-export a named export as the default export:
export { Button as default } from './Button.js';
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. |