Stage: 0
Author: Guy Bedford
Reviewers: Caridy Patiño
Specification: http://guybedford.github.io/proposal-export-star-default/
In the current specification, export * from 'module'
will export all named exports, except for
the default export.
There are use cases though where it is useful to be able to have the default
export defined within
an internal private module, and have that exposed through export *
.
Consider a module X with both a named export and a default export that is re-exported through another module.
We then re-export this module with explicit names in reexport1.js
and with export *
in reexport2.js
:
x.js
export default 'default';
export let name = 'name';
Re-export variations:
reexport1.js
export { name, default } from './x.js';
reexport2.js
export * from './x.js';
In the first variation, we can import the default as it was exported explicitly:
import { name } from './reexport1.js';
import { default } from './reexport1.js';
import * as A from './reexport1.js';
A.default;
// -> 'default'
while in the export *
re-export variation, the default is not available:
import { name } from './reexport2.js';
import { default } from './reexport2.js';
// Syntax Error: 'default' is not exported
import * as B from './reexport2.js';
B.default
// -> undefined
This breaks the the user intuition that default
is a named export like any other.
In NodeJS it is a convention to have an index.js
as the main entry point of the package.
If we want to create this module using export *
statements to expose modules from sub-folders, we
cannot export the default in this way:
index.js
export * from './lib/package-core.js';
export let extraInfo = 'abc';
If lib/package-core.js
contained a default export, we would not be exposing it.
We would need to explicitly export the default with:
index.js
export * from './lib/package-core.js';
export { default } from './lib/package-core.js';
export let extraInfo = 'abc';
in order to ensure we get the expected entry point module value.
The issue here is that if we were ever to remove the default export from
lib/package-core.js
, we would then get a SyntaxError in index.js
that the
default export does not exist anymore. The change of removing the default export
would need to be made in two separate places.
The above pattern is also already widely seen in index.js
modules on npm, of the form:
module.exports = require('./lib/package.js');
where we know that the index.js
module will exactly match the internal module.
The case for re-exporting the default can also be extended to use cases where we want to dynamically generate a module wrapper exposing the exports of another module, without knowing in advance its export names.
Consider a use case such as an npm CDN, which allows shortcut URLs like https://npmcdn.com/lodash
,
which then exposes a module at a full versioned URL like https://npmcdn.com/[email protected]/index.js
.
A dynamically generated module for https://npmcdn.com/lodash
could look like:
export * from './[email protected]/index.js';
but if there was a default export we would have to provide a different response:
export * from './[email protected]/index.js';
export { default } from './[email protected]/index.js';
If we don't include the default export when it is needed we may miss the default
, but if do we include
it when it isn't needed we will get an error. This restriction has thus added a new static analysis
concern to what would otherwise be a simple server response.
The proposal is to remove special-casing of default
for export *
in the current specification
in order to simplify the symmetry of export *
as well as to enable easier module wrapping use cases as described above.
The change is backwards-compatible with the existing specification and results in disabling the syntax error for importing
a default export through an export *
, and making default
available as a named export on the module namespace object for this case.
http://guybedford.github.io/proposal-export-star-default/
One concern with including default in export *
is how to handle collisions, but export *
is already designed to handle ambiguity well - when two export *
statements in a module resolve the
same export name, a SyntaxError is thrown as in 15.2.1.16.4 12.d.ii):
x.js
export default 'x';
export let name = 'nameX';
y.js
export default 'y';
export let name = 'nameY';
z.js
export * from './x.js';
export * from './y.ys';
When we try to import name
via
import { name } from './z.js';
we will get a SyntaxError that name is ambiguous.
This conflict can be resolved by explicitly indicating a module to export name
from:
z.js
export * from './x.js';
export * from './y.ys';
export { name } from './x.js';
Now when we import name
from z.js
it will be correctly resolved.
With this spec change the same ambiguity resolution process would apply to the default export:
import { default } from './z.js';
would throw a SyntaxError.
z.js
export * from './x.js';
export * from './y.ys';
export { default } from './x.js';
would then resolve that SyntaxError by providing an explicit precedence, providing symmetry between handling conflicts between named exports and default exports via export *
.