In an ES module barrel file, what is the difference between writing `export { parse } from './parser.js'` and writing `import { parse } from './parser.js'; export { parse };`?
answer
- both reach the same original declaration
- one of them adds nothing to scope
- try calling it inside the barrel
- forwarding versus actually using the value
basics
~20 sConsumers see the same export either way, but the from-form creates no local binding: the barrel itself cannot reference parse. The import-then-export version does bind parse in module scope, so the file can also use the value.
solid answer
~40 sFor anyone importing from the barrel the two are equivalent — both expose an export named `parse` that resolves through to the original declaration in `parser.js`. The difference is inside the barrel. `export { parse } from './parser.js'` is a single re-export declaration: it adds an export entry and nothing else, so `parse` is not in the file's scope and referencing it there throws a ReferenceError. The two-statement version performs a real import first, so `parse` is a normal module-scoped binding the file can call, wrap or log, and then exports it. Use the from-form for pure forwarding — it states the intent, keeps the file free of unused local names, and avoids `no-unused-vars` noise. Use the import-then-export form only when the module genuinely needs the value as well as re-exposing it.
code
javascript · 9 lines// parser.js
export function parse(s) { return JSON.parse(s); }
// barrel.js
export { parse } from './parser.js'; // forwards only, no local binding
export function describe() {
return typeof parse; // ReferenceError: parse is not defined
}go deeper
Remember that the from-form only forwards: it adds an export but does not give the current file a variable it can use. Import the name separately if the file needs to call it.
State the mechanic plainly — a re-export declaration creates an export entry and no local binding — and show the ReferenceError that follows from assuming otherwise.
Argue the house style: pure forwarding uses the from-form so barrels stay free of unused locals and the intent is visible, while an import is justified only when the module consumes the value. Know that wrapping requires a distinct declared value.
Decide when a package should expose a hand-written barrel at all, and what belongs in it. Be able to say what a forwarding-only entry point costs in load and maintenance versus letting consumers import deep paths directly.
## The same public result, a different local effect Both spellings put an export named `parse` on the barrel module, and in both cases that export resolves through to the original declaration in `parser.js` rather than to a copy made by the barrel. A consumer cannot tell them apart. The difference is entirely about what exists inside the re-exporting file. ```js // barrel-a.js export { parse } from './parser.js'; // parse is NOT a binding here // barrel-b.js import { parse } from './parser.js'; export { parse }; // parse IS a binding here and can be used ``` In `barrel-a.js`, adding `console.log(parse)` throws a ReferenceError: the declaration created an export entry, not a variable. In `barrel-b.js` the same line works, because the import statement introduced a real module-scoped binding that the export clause then names. ## Why the distinction exists The from-form is a dedicated grammar for forwarding. Keeping it separate from an import means a barrel can forward a hundred names without polluting its own scope with a hundred identifiers, and without any risk of an internal declaration accidentally shadowing one of them. It also makes the intent legible in a diff: this file exposes something it does not itself use. There is a practical tooling consequence too. In the two-statement version, linters and dead-code passes see a local binding whose only use is the export clause; most handle it correctly, but the one-line form removes the question entirely. And a reader scanning the file does not have to check whether `parse` is used somewhere below. ## When you actually need the import Reach for the two-statement version only when the module needs the value for itself as well as forwarding it: ```js import { parse } from './parser.js'; export { parse }; export function parseAll(inputs) { return inputs.map(parse); // needs parse locally } ``` Here the import is not ceremony; it is the only way to call `parse`. Trying to write this with a from-style re-export alone would fail at the call site. ## Wrapping is a third thing entirely A common follow-on mistake is to think the two-statement form lets you *change* what consumers get. It does not — exporting the same binding under the same name is still forwarding. If you want to intercept, you must export a different value: ```js import { parse as rawParse } from './parser.js'; export function parse(input) { return rawParse(input.trim()); } ``` Now the barrel exports its own function that happens to be called `parse`, and the original is reachable only through the local alias. ## Related forms in the same family The from-clause re-export supports the same renaming and grouping as an ordinary export clause: `export { parse as parseText, format } from './text.js'` forwards two names, one of them renamed. A file may contain any number of these declarations, and each may target a different specifier. Like every import and export declaration they must appear at the module's top level — you cannot put one inside an `if` block or a function body. ## What an interviewer wants to hear The crisp answer is one sentence: the from-form re-exports without importing, so no local binding is created. The follow-on judgment is knowing which to use — pure forwarding gets the from-form as a matter of style and clarity, and the import version is reserved for the case where the module actually consumes the value. Candidates who insist the two are identical have usually never tried to reference a re-exported name in a barrel and watched it throw.
- If a barrel wants to forward a name and also wrap it with extra behaviour, how should it be written?Import the original under an alias and export your own function under the public name: `import { parse as rawParse } from './parser.js'` then `export function parse(input) { return rawParse(input.trim()); }`. Re-exporting the same binding under the same name cannot intercept anything — it is pure forwarding, so a wrapper has to be a distinct value the barrel declares itself.
- Can a re-export declaration appear inside an if block so a barrel forwards different modules per environment?No. Import and export declarations, including from-clause re-exports, may only appear at a module's top level, and the specifier must be a static string literal. Conditional forwarding has to be built some other way — for example by choosing what to export from a value computed at evaluation time, or by using dynamic import for a truly conditional load.
saying these in an interview costs you the question
- Claims the two forms are interchangeable in every respect
- Expects to call a re-exported name inside the barrel
- Thinks re-exporting the same name lets you wrap the value
- Believes the barrel makes a copy of the exported value
- Puts a re-export inside a conditional block