Skip to content

Instantly share code, notes, and snippets.

@mariusGundersen
Created June 9, 2014 22:07
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mariusGundersen/88a4c5690e08da0d07f6 to your computer and use it in GitHub Desktop.
Save mariusGundersen/88a4c5690e08da0d07f6 to your computer and use it in GitHub Desktop.
ES6 modules

Currently you can export things from a module in at least six different ways, and import things in at least six different ways, resulting in 36 different combinations, most of which are not semantically valid.

Here is a greatly simplified (and probably naive) suggestion for modules in ES6:

###export You can only export named things, including variables and functions.

let a = "hello";
export a;
export const b = {number: 5, text: "World"};
export function add(a, b){
  return a + b;
};

###import The module consists of named bindings, which can be imported by other modules:

import {a, b, add} from "myModule";

console.log(add(a, b.text)); //helloWorld

static analysis can be used to ensure that the imported values exist in the exported module. If any of them are missing a load time error is produced.

ES6 object destructuring can be used when importing modules:

import {a: hello, b: {text: world}} from "myModule";

console.log(add(hello, world)); //helloWorld

Static analysis cannot be used to ensure that destructuring is correct, so a runtime error is produced if the destructuring does not work.

All of the exported bindings can be imported as a single object if the {} is not used in the import statement:

import myModule from "myModule";

console.log(myModule.add(myModule.a, myModule.b.text)); //helloWorld

Again, static analysis is not possible, so a runtime error is produced if the properties does not exist in the module object.

This is great for libraries that export many things, for example underscore:

import _ from "underscore";

console.log(_.map([1, 2, 3], a => a*2); //2, 4, 6

###destructured export export default is replaced with object destructuring:

export var {a, b} = {a:"hello", b:"World"}
//this is the same as
export var a = "hello";
export var b = "World";

Destructured export can be combined with normal export, as long as no binding name is used twice. Static analysis can be used to check this, so a load time error can be produced if the module violates this.

Using destructured export existing code which produces an object of functions (like underscore) can be modernized and made to work with ES6 modules:

var _ = {
  //The existing underscore code
};

export var {map, reduce, each, filter} = _;

The module is always an object, never a function or string, etc. This makes modules easy to reason about. If you expect a module to export a single function, you can know for certain that you need to wrap the import binding in curly braces. If you want to export a single function, export it like you would anything else:

export function $(){ /* ... */ };
//and to import it:
import {$} from "jQuery";
$('body');
@KidkArolis
Copy link

  1. at this point, I'd rather have many ways of doing this and the community will find out what works best in practise, instead of picking this way, which goes against what the community has found to be the best practises so far (small, single, default exports). This import {x} from "y" form you're suggesting encourages modules with many exports, whereas it's often preferable to have modules that export a single thing (a class or a function).
  2. I'm starting to get the feeling that we way overemphasise the importance of static checking which leads to sacrificing the module ergonomics. This is JavaScript after all, do we really need the static analysis here?
  3. Modules like underscore (many exports) are the exception, not the rule (?), so we shouldn't optimise for those. (I think some guys are trying to validate this or a similar question by looking at all modules at npm).

@mariusGundersen
Copy link
Author

I disagree that both should be implemented. It is a lot easier to add something to the spec later than to remove something which is found to be useless (or dangerous). Either one of defaultExport (single) or namedExport (multiple) should be used, but not both.

If defaultExport is used, then static analysis is sacrificed. This might be OK, as you said JS developers aren't used to static checking anyways, and most tools can work with the dynamic nature of JS to get static-like analysis to work.

But if defaultExport is used, then import destructuring should probably still be supported, in the cases where a module exports an object with multiple properties.

@KidkArolis
Copy link

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