Idea: could we make the "default default export" the module instance object?
So for:
// a.js
export const x = 1;
export const y = 2;
We get, in this new world:
// b.js
import a from 'a';
import { x, y } from 'a';
assert.strictEqual(a.default, a);
assert.strictEqual(a, System.get('a'));
assert.strictEqual(a.x, x);
assert.strictEqual(a.y, y);
In this world, we can remove module a from 'a'
without consequence.
Consider another version of the module 'a'
above, where the author naively adds a default export:
// a.js, v2
export const x = 1;
export const y = 2;
export default () => console.log('boo');
The author of 'a'
has made a backward-incompatible change, in a somewhat-nonobvious way. If they were not testing the import a from 'a'
form before, they won't even notice, and will break their users, since now:
// b.js
import a from 'a';
import { x, y } from 'a';
assert.strictEqual(a.default, a); // fails; a is a function, not the MIO
assert.strictEqual(a, System.get('a')); // fails; a is no longer the MIO
assert.strictEqual(a.x, x); // fails
assert.strictEqual(a.y, y); // fails
By being more careful, the upgrade can be more seamless:
// a.js, v2.0.1
export const x = 1;
export const y = 2;
const defaultExport = () => console.log('boo');
defaultExport.x = x;
defaultExport.y = y;
export default defaultExport;
This will fix the latter two assertions, concerning a.x
and a.y
, which are in practice what the user will be using. The former two will still fail, however. (The one comparing a.default
and a
could be fixed, I suppose, but nobody would bother.)
If the default export were a primitive, then you could not assign properties to it, so there is no way of implementing the partial solution at all.
If x
and y
were not constants, but instead were mutated throughout the lifecycle of the module, then a more complicated incantation would be necessary:
// a.js, v3
export let x = 1;
export function changeX(value) { x = value; };
const defaultExport = () => console.log('boo');
Object.defineProperties(defaultExport, {
x: {
get() { return x; },
enumerable: true
},
changeX: {
value: changeX,
enumerable: true
}
});
export default defaultExport;
People are unlikely to do this, but then again, they are somewhat unlikely to use mutable bindings.
Consider a module like this:
// binder.js
export function bind() { };
export default () => console.log('hiii');
The consumer is going to get in trouble. What worked before for x
and y
will not work for bind
:
import binder from 'binder';
import { bind } from 'binder';
assert.strictEqual(binder.bind, bind); // fails; binder.bind === Function.prototype.bind
This is not really solvable, as it's inherent in the model. A similar problem appears in CommonJS/AMD:
// binder-cjs.js
module.exports = () => console.log('hiii');
// Should I override the existing bind?! People will not be happy.
module.exports.bind = function bind() { };
The same problem can occur with e.g. exporting constructor
for objects:
// constructorer.js
export function constructor() { };
export default { foo: 'bar' };
import constructorer from 'constructorer';
import { constructor } from 'constructorer';
assert.strictEqual(constructorer.constructor, constructor) // fails; constructorer.constructor === Object.prototype.constructor
This will certainly not be a problem for porting existing code, but the fact that it's allowed creates definite confusion.
Once again, if we remove
export default
, all of these complications vanish. They are caused byexport default
.