ES2015 Modules 101

A brief overview on how to use ES2015 modules.

ES2015 Modules 101

I've been playing around with ES2015 Modules constantly for some months now. At Sparwelt we've finally finished migrating all our projects from AMD to the new ES2015 standard — of course, with a great help from Webpack and BabelJS, but let's leave that to an eventual later post.

Although I've had something I would call a late start, some information wasn't exactly easy to find and documentation at some points may have confused me. So, I'll write down some tips and concepts I'd like to have found sooner than later to provide an smoother beginning (and possibly to give you that last push to start).

import and exporting

To keep things simple, consider there are 4 ways of carrying data across your modules. We are going to classify those ways based on the effect they have on the module where they are imported.

  • Named exports (a constant)

    import { variable } from './module.js';

I see this as the most concise and common use of a module, this variable could carry actually any kind of information you wish to export from *module.js. *The export statement is also quite straightforward and simple, check it out:

export const foo = 'a constant here and wherever else I want';

While we're on this, let's have it said that a single import/export statement can carry multiple variables. As in:

// -- module.js
/* ... */
export { foo, bar, baz }

// -- main.js
import { foo, bar, baz } from './module.js'
  • Default Exports (usually an anonymous function or a class)

    import MyClass from './Class.js';

This would be the second most used way, and probably the most necessary to keep your code organised and with its concerns separated. If by the import the difference isn't explicit, let's first check the export before going deeper on the why.

export default class {
  // ...
}

As you can see, there's another keyword on the statement. And that means there can be only one default export per module. You can combine this type of export with the one mentioned above, therefore you're able to import the same module in different ways along your code — as long as you use export default only ONCE per module. It would look something like the following:

import MyClass, { foo, bar, baz } from './module.js'
  • The entire content of the file as an object
import \* as Module from './module.js';

This code, brings the entire content (upper scoped variables, functions, etc) from the given module and encapsulates it in the Module *object, in that way, the variable *foo *present in module.js *would be accessible through as a property of this imported object (Module.foo)

  • Nothing. Only execute whatever code is in the file.
import './module.js'

In this way, no content from our module.js will be available, though, when your import statement is evaluated, the code inside the file will run.

alias

I believe it's common sense that one of the main criteria to have good, maintainable code is to increase the readability of this given code.

Personally, I like my code to be as self-explanatory as possible, allowing my team members to deduce the "how"s and the "why"s just by reading the code itself, relying on comments just as a last resort for those parts where I wasn't able to make it clean and neat.

So, now that we've covered how to carry the data around, we can also rename it to make sure its name is coherent with the functionality it provides in the context it's imported. It's simple as pie:

import oldName as newName from './module.js';
import { foo as bar } from './module.js';

Done. And as I said before, good code is self-explanatory.

Combine declarations

As you may have deducted or seen by now, import declarations can be mixed to simplify your code, basically what will tell you how many import declarations you need is the number of files you need to make use of. Some examples are below:

import * as All, { foo as bar } from './mod1.js';
import Class, { var1, var2, var3 as baz } from './mod2.js';
import SomeDefault, * as Everyone from './mod3.js';

Catches

  • All imported views (variables, functions, etc) are read-only.

  • ES2015 Modules support cyclic-dependencies out-of-the-box. (though relying on them would be a code-smell most likely)

  • All imports are and must-be top levelled

  • imports are always hoisted*

Hopefully this was useful for you and encouraged you to start playing with this new syntax. If you have any feedback or questions feel free to comment here or call me up on twitter, I'll be happy to help! ;)