91

Can I destructure a default export object on import?

Given the following export syntax (export default)

const foo = ...
function bar() { ... }

export default { foo, bar };

is the following import syntax valid JS?

import { foo, bar } from './export-file';

I ask because it DOES work on my system, but I've been told it should NOT work according to the spec.

sfletche
  • 47,248
  • 30
  • 103
  • 119
  • Good question I look there but they also use `export default { a, b}` http://exploringjs.com/es6/ch_modules.html – Jonathan Dion May 05 '17 at 23:09
  • 1
    Who said that it should not work? But I agree with them, it should not work. Can you provide your environment settings or share a demo project somewhere? – Bergi May 15 '17 at 18:52
  • @Bergi: you did :) But I don't see this syntax constraint spelled out in the spec... – sfletche May 15 '17 at 19:37
  • To destructure on import you can export by defining an intermediate object `const obj = {foo, bar}` then `export const { foo, bar } = obj` – 647er Feb 04 '21 at 18:55

2 Answers2

135

Can I destructure a default export object on import?

No. You can only destructure an object after importing it into a variable.

Notice that imports/exports have syntax and semantics that are completely different from those of object literals / object patterns. The only common thing is that both use curly braces, and their shorthand representations (with only identifier names and commas) are indistinguishable.

Is the following import syntax valid JS?

import { foo, bar } from './export-file';

Yes. It does import two named exports from the module. It's a shorthand notation for

import { foo as foo, bar as bar } from './export-file';

which means "declare a binding foo and let it reference the variable that was exported under the name foo from export-file, and declare a binding bar and let it reference the variable that was exported under the name bar from export-file".

Given the following export syntax (export default)

export default { foo, bar };

does the above import work with this?

No. What it does is to declare an invisible variable, initialise it with the object { foo: foo, bar: bar }, and export it under the name default.
When this module is imported as export-file, the name default will not be used and the names foo and bar will not be found which leads to a SyntaxError.

To fix this, you either need to import the default-exported object:

import { default as obj } from './export-file';
const {foo: foo, bar: bar} = obj;
// or abbreviated:
import obj from './export-file';
const {foo, bar} = obj;

Or you keep your import syntax and instead use named exports:

export { foo as foo, bar as bar };
// or abbreviated:
export { foo, bar };
// or right in the respective declarations:
export const foo = …;
export function bar() { ... }
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 5
    i've read countless times that 'Defaults exports are favored` and provides simpler syntax. But doesn't the need to import the default object and then destructure the contents, run counter to that claim? – sfletche May 16 '17 at 14:45
  • 11
    @sfletche Yes, that claim is wrong. When you have multiple variables, then named exports are favoured. Default exports only are favoured when there is only a single major value to export (e.g. a class). Default exports have simpler syntax because they are frequently needed, not because they are generally favoured. – Bergi May 16 '17 at 16:45
  • +1. This is mostly correct although I would say that the syntax for importing named exports is likely an _attempt_ at a partial analogy with object destructuring. Unfortunately the analogy breaks down for a number of reasons but none are related to `default`. Rather, it breaks down due to the unnecessary `as` keyword. I think importing named imports, when no aliasing is involved, is at leas akin to destructuring the implicit Module Namespace Object. – Aluan Haddad May 18 '17 at 03:24
  • 1
    @AluanHaddad The analogy mostly breaks down because imports set up dynamic bindings (references to variables), very much unlike running a destructuring which requires the property values to already exist. – Bergi May 18 '17 at 03:44
  • 1
    Well the named imports are _also_ required to already exist (some transpilers/module formats that incorrectly implement the spec violate this). You are however correct that liveness is a major difference. – Aluan Haddad May 18 '17 at 03:53
  • 1
    his default export statement is creating an object literal using shorthand syntax, he is exporting a single object as does exporting named exports adds properties to an object. The import statement is correct. Your answer is wrong. – Rafael May 18 '17 at 20:16
  • @Rafael "*as does exporting named exports adds properties to an object.*" - uh, no. `import * as obj …` does that. His import statement does not match the export statement. Try it yourself, or read the spec! – Bergi May 18 '17 at 20:57
  • You should read the spec: https://www.ecma-international.org/ecma-262/6.0/#sec-module-semantics-static-semantics-exportednames – Rafael May 18 '17 at 21:16
  • @Rafael Yes, as you can see there (or better, [there](https://www.ecma-international.org/ecma-262/6.0/#sec-exports-static-semantics-exportednames)) the export declaration in the OP only exports a single name, `default`. Or more specifically, an entry with [[LocalName]] `"*default*"` and [[ExportName]] `"default"`. – Bergi May 18 '17 at 21:21
  • 1
    `ImportEntries` are determined by the exports. Which are thereby imported creating another `List` to house the entries. You can't import what hasn't been exported, this relationship is defined by the exports not the import statement. – Rafael May 18 '17 at 21:21
  • 1
    The name does not matter. The data structure is the only relevant thing. The notation denotes a `List` is returned. As I stated and you objected. – Rafael May 18 '17 at 21:25
  • @Rafael Yes, a list with one entry. I see that. And the ImportEntries will be a list with two entries, one with [[ImportName]] and [[LocalName]] `"foo"`, the other with [[ImportName]] and [[LocalName]] `"bar"`. And then when it tries to resolve them, it'll only find `default` and fail. – Bergi May 18 '17 at 21:32
  • 1
    The reason his code doesn't run is because it's not complete in the js engine itself. It is valid ecmascript, and should work when its feature complete. The static semantics define this, https://www.ecma-international.org/ecma-262/6.0/#sec-module-semantics-static-semantics-varscopeddeclarations. You are focused on the shim, transpilers are using. A `Module: List` – Rafael May 18 '17 at 21:39
  • @Rafael I don't claim that the modules are not valid JS, I claim that it throws a syntax error when trying to resolve them against each other with the algorithm in the spec (I don't know what transpilers are doing, possibly giving too much leeway to commonjs modules). I don't see what `VarScopedDeclarations` has to do with this – Bergi May 18 '17 at 21:49
  • 1
    [Module Requests](https://www.ecma-international.org/ecma-262/6.0/#sec-module-semantics-static-semantics-modulerequests). Under every module request a `List` is returned. The only reason his code does not work is because transpilers do not return a proper `ModuleItem` (a `List` with `ModuleEntries`). His question was, "is the import statement valid js"...the correct answer is yes. – Rafael May 19 '17 at 12:30
  • @Rafael It doesn't work because the module requests cannot be matched to the export entries. Have you tried to [resolve](https://www.ecma-international.org/ecma-262/6.0/#sec-resolveexport) them? – Bergi May 19 '17 at 14:36
  • @Bergi "I don't claim that the modules are not valid JS" The question is specifically about the spec, and you give a bolded "**No.**" as an answer. It is not obvious that you don't make that claim. – Corey Jul 20 '21 at 23:02
  • 1
    @Corey The bolded "No" is an answer to the question in the blockquote, "*Does the given (default) export syntax work with the given (named) import?*". The spec says that this combination should throw an error. Both declarations are valid JS syntax in modules, they just don't fit together. – Bergi Jul 20 '21 at 23:19
11

Can I destructure a default export object on import?

Yes, with Dynamic Imports

To add to Bergi's answer which addresses static imports, note that in the case of dynamic imports, since the returned module is an object, you can use destructuring assignment to import it:

(async function () {
  const { default: { foo, bar } } = await import('./export-file.js');
  console.log(foo, bar);
})();

Why this works

import operates much differently in different contexts. When used at the beginning of a module, in the format import ... from ... , it is a static import, which has the limitations discussed in Bergi's answer.

When used inside a program in the format import(...), it is considered a dynamic import. The dynamic import operates very much like a function that resolves to an object (as a combination of named exports and the default export, which is assigned to the default property), and can be destructured as such.

In the case of the questioner's example, await import('./export-file.js') will resolve to:

{
  default: {
    foo: ...,
    bar: function bar() {...}
  }
}

From here, you can just use nested destructuring to directly assign foo, and bar:

const { default: { foo, bar } } = await import('./export-file.js');
Steve
  • 10,435
  • 15
  • 21
  • Could you please explain a bit more how this works? – stratis Nov 04 '20 at 18:38
  • 1
    Updated my answer – Steve Nov 04 '20 at 19:32
  • 1
    Thank you! Makes much better sense now! – stratis Nov 04 '20 at 19:42
  • I find that even though this might work, visually I would prefer to take the accepted answer's first approach by separating it out into 2 statements favouring readability. Also having that `await` in the import and no outer `async` around confuses me. – Shaun McCready Jul 12 '22 at 19:50
  • 1
    @ShaunMcCready, I believe the spirit of this question (and other questions like these) pertains to what is *possible*, rather than what coding style is *preferable*. Generally I agree with you, and would say that more verbose style is generally preferable to nested destructuring. But that is beside the point when the question asks whether something is possible. Also note that the accepted answer and this answer are not syntactically equivalent. This one uses dynamic imports and the other uses static imports. They are significantly different. – Steve Jul 15 '22 at 01:08