Skip to content

Instantly share code, notes, and snippets.

@domenic
Last active August 29, 2015 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save domenic/23dfe87fc921735de04c to your computer and use it in GitHub Desktop.
Save domenic/23dfe87fc921735de04c to your computer and use it in GitHub Desktop.
Default default export = MIO?

Intro

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.

Complication #1: Upgrading

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

Partial Solution

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.)

Complication #1A

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.

Complication #1B

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.

Complication #2: collisions

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.

@johnjbarton
Copy link

Once again, if we remove export default, all of these complications vanish. They are caused by export default.

@zenparsing
Copy link

Good analysis!

Yeah - I think this further illustrates that export default is terribly confusing for users.

Edit: meaning the export default syntactic production specifically, not necessarily the concept of "default" imports or exports.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment