Skip to content

Instantly share code, notes, and snippets.

@tj
Last active September 30, 2017 19:13
Show Gist options
  • Save tj/df9ab95d97dbcd1c0ded to your computer and use it in GitHub Desktop.
Save tj/df9ab95d97dbcd1c0ded to your computer and use it in GitHub Desktop.
ES6 modules

Ok so http://wiki.ecmascript.org/doku.php?id=harmony:modules looks more less the same as it did last time I looked, however that document combined with https://github.com/rwaldron/tc39-notes/blob/master/es6/2014-06/jun-5-modules.md#do-we-need-the-module-foo-from-foo-import-syntax looks like we're conflating quite a few concepts (IMO... not trying to anger anyone).

Personally I'm in favour for removing module as mentioned in the gist. I would also remove re-exporting (export * from "crypto"), even if it's a nicety I can't say out I've done much re-exporting in the thousands and thousands of modules I've written, does anyone else do this often? (not sure)

Single exported value

Personally (barring weird edge-cases I'm not aware of?) I would love if we only had exporting a single or multiple value, with only one syntax for importing. For example the common use-case of exporting a single function or value:

ferret.js

export {
  name: 'tobi',
  color: 'white'
}

some.js

import tobi from "ferret"

The word "from" would arguably be incorrect here, so maybe "as" or no keyword at all, just import tobi "ferret" like Go does (reads a little weird?).

Multiple exported values

I'm assuming we can detect multiple exports without using export default since they're static:

ferrets.js

export tobi {
  name: 'tobi',
  color: 'white'
}

export loki {
  name: 'loki',
  color: 'brown'
}

or with = (less confusing?)

ferrets.js

export tobi = {
  name: 'tobi',
  color: 'white'
}

export loki = {
  name: 'loki',
  color: 'brown'
}

other types:

ferrets.js

export tobi = 'Tobi Ferret'
export loki = 'Loki Ferret'

some.js

import ferrets from "ferrets"
ferrets.tobi
ferrets.loki

some.js

import {tobi, loki} from "ferrets"

I'm not sure if there was reasoning behind doing export let foo = 'bar' and export function foo(){ but to me it's more confusing and requires more reading than having the name on the far left. Although in the case of a function, and now classes (?) you need the name anyway, so I can understand that, but it definitely reads weird to me.

The syntax of exports is sort of bikeshedding on my behalf but the main thing I'm wondering about is why we can't just have the two keywords and maybe the destructuring.

@domenic
Copy link

domenic commented Jun 15, 2014

What are tobi and loki in the last example? Lets, vars, consts, functions, or classes?

@tj
Copy link
Author

tj commented Jun 15, 2014

one sec I'll add some more examples

@tj
Copy link
Author

tj commented Jun 15, 2014

updated with =, that does look confusing without

@domenic
Copy link

domenic commented Jun 15, 2014

Still unclear though if they're lets, vars, or consts. Which impacts how the rest of the exporting file interacts with them. E.g. what does

export tobi = 'Tobi Ferret'
tobi = 'Some other ferret'

do? What about

export tobi = 'Tobi Ferret'
var tobi = 'Some other ferret'

?

I think you'll find that including the keywords is not that bad, as it adds clarity around all these kind of things.

@tj
Copy link
Author

tj commented Jun 15, 2014

lets and vars would behave the same since they're root anyway wouldn't they? I get the argument for including those, perhaps default to var so it's more like node's exports.foo = bar?

export put = (key, val) => { stuff }
export get = (key) => { stuff }

I'm not sure how that would apply to classes, are they expressions? (that might have been coffeescript), but I can see that looking weird if they're not, in which case the statement-level export in the current spec makes sense. It's unfortunate that it can't look a bit better for the common cases

@domenic
Copy link

domenic commented Jun 15, 2014

The syntax of exports is sort of bikeshedding on my behalf but the main thing I'm wondering about is why we can't just have the two keywords and maybe the destructuring.

The problem is that there's a conflict between what the designers of ES6 modules want, where multiple exports are a distinct "thing", and what the community is used to, where multiple exports just means exporting an object with some properties.

This gets particularly hairy when combined with default exports. We're used to doing default exports, and if we want other stuff, we use properties of that export. E.g.

module.exports = function (...) { };
module.exports.sync = function (...) { };
var glob = require("glob");
glob(...);
glob.sync(...);

They would like a world where the sync export becomes a "real export", which can be statically checked, bound across module boundaries, etc. So they'd do

export default function (...) { };
export function sync(...) { };
import glob from "glob"; // sugar for import { default as glob } from "glob";
import { sync } from "glob";

glob(...);
sync(...);

or even

import glob, { sync } from "glob";

Note that in these cases, glob.sync will not work like we're used to.

Thus, the on-its-way-out module x from "y" syntax. It would always be an object, containing all exports.

module globM from "glob";
import { sync }, glob from "glob";

globM.default === glob;
globM.sync === sync;
typeof globM === "object";

This would be most useful for modules with lots of exports, like fs.


A final thing to consider: the existing CommonJS pattern falls down hard if you want to combine a default export with another export named e.g. constructor or bind. See https://gist.github.com/domenic/23dfe87fc921735de04c for more musings on that.

@domenic
Copy link

domenic commented Jun 15, 2014

lets and vars would behave the same since they're root anyway wouldn't they?

Nah, you can't use a let before it's declared, is the difference.

I'm not sure how that would apply to classes, are they expressions?

They are actually. But it's useful to have the declaration so that it's available in the rest of the module. That is:

export default class X { }; // export default <declaration>
new X(); // works

vs.

export default (class X { }); // export default <expression>
new X(); // fails

It's unfortunate that it can't look a bit better for the common cases

There actually was a proposal to default export x = y to export let x = y, I think. (Personally I would prefer const :P.) We might resurrect it in the future if people like it enough.

@tj
Copy link
Author

tj commented Jun 15, 2014

Gotcha, that sounds reasonable. I wouldn't say exporting properties on whatever you exported as the module itself is the best pattern, but I have definitely done it myself. I don't have strong feelings for or against that feature, but keeping it simple wouldn't be awful.

Go's export mechanism is even more restrictive than we're talking here, and while it's annoying at times because you can't have a "default" export, the simplicity is really nice. Ditching that one pattern might not be a bad thing.

Cool, with classes being introduced that makes more and more sense. A const default would be great!

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