Skip to content

Instantly share code, notes, and snippets.

@domenic
Last active June 29, 2022 01:05
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save domenic/5438322 to your computer and use it in GitHub Desktop.
Save domenic/5438322 to your computer and use it in GitHub Desktop.
Default anonymous export = module instance object

Kevin's "default anonymous export" idea.

Let anon be the symbol denoting anonymous export.

Assume the following syntaxes:

export default [Expression]; // sets the anonymous export
export [Declaration] // sets a named export

import <[Identifier]> from "module url"; // imports named export
import [Identifier] from "module url"; // imports anonymous export
import "module url" as [Identifier]; // imports module instance object (MIO)

Scenario 1: anonymous export only

// foo.js

export default "this is default";


// bar.js

import x from "foo.js";
assert(x === "this is default");

import "foo.js" as mio;
assert(mio[anon] === x);

Scenario 2: named exports only

// foo.js
export function aFunction() { };


// bar.js

import <aFunction> from "foo.js";
assert(typeof aFunction === "function");

import x from "foo.js"; // `x` is the MIO by default!
assert(typeof x.aFunction === "function");

import "foo.js" as mio;
assert(mio === x);
assert(mio[anon] === x);

This differs from the current thinking in that import x from "foo.js" returns the MIO, instead of failing.

Scenario 3: mixing them together

// foo.js

export function aFunction() { };
export default "this is default";


// bar.js

import <aFunction> from "foo.js";
assert(typeof aFunction === "function");

import "foo.js" as mio;
assert(mio !== x);
assert(mio[anon] === x);

import x from "foo.js"; // `x` is the anon export we explicitly set, instead of the MIO.
assert(typeof x.aFunction === "undefined");
assert(x === "this is default");

Notes

Refactoring Hazard Avoided

This helps the refactoring hazard identified in https://gist.github.com/domenic/4754483#refactoring-example, allowing libraries to transparently switch from MIOs to objects-or-functions with properties.

E.g., with this proposal, you could have

// libV1.js

export function async() { };
export function sync() { };


// consumer.js

import lib from "libV1.js";

lib.sync();
lib.async();


// libV2.js

export function sync() { };
export function async() { };

function theDefault() {
  // User testing has revealed that async is by far the most common operation.
  return async();
}

theDefault.sync = sync;
theDefault.async = async;

export default theDefault;

Without Kevin's proposal of a default anonymous export equal to the MIO, with version 1 of the library the consumer would have to be using

import "libV1.js" as lib;

But that would break the moment he switched to version 2 of the library! He'd be required instead to do

import lib from "libV1.js";

This is a notable downside of current proposals vs. Node.js or AMD.

Is import ... as necessary anymore?

There are very few cases anymore where import ... as has any utility. Namely, it only matters when you are using a library that:

  • Has overriden the default anonymous export
  • Is also using named exports
  • Does not expose those named exports as properties of the anonymous export
  • Has enough properties that consumers usually want to import the module wholesale, instead of piecewise using import <[Identifier]> syntax.

Conclusion

I'm on the fence on this one. It's pretty nice, but doesn't feel that clean, y'know?

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