Given that exporting bindings is different from how data is normally transported in JavaScript – why do it this way? It has the benefit of making it easier to deal with cyclic dependencies. The following code is an example of a cyclic dependency:

a.js imports bar from b.js, which means that b.js is executed before a.js. But how can b.js access foo then, if a.js hasn’t provided a value for it, yet? b.js imports a binding, which initially refers to an empty slot. Once a.js is executed, it fills in that slot. Therefore, b.js only has a problem if it uses foo in the top level of its body, while it is executed. Using foo in entities that are accessed after the evaluation of a.js are fine. One such entity is the function bar().

This may seem like an theoretical exercise, but cyclic dependencies can happen relatively easily in large code bases, especially during refactoring. Cycles tend to be longer (for example: m1 imports m2 imports m3 imports m4 imports m1), but the problem is the same.

Default exports of hoistable declarations (function declarations, generator declarations) and class declarations are similar to normal inline exports in that named local entities are created and tagged.

All other default exports are about exporting the results of expressions.

Re-exports are handled differently from normal exports. A re-export does not have a local name, it refers to the re-exported entity via that entity’s module and export name (shown in the column “Import name” below).

ES6 transpilers compile ES6 modules to ES5. Due to the completely new way of passing on data (via bindings), you should expect the ES5 version to not always be completely compliant with the ES6 spec. Things are even trickier when transpiled ES6 code has to interoperate with native CommonJS or AMD modules.