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); // failsBy 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.bindThis 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.constructorThis 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.