ES6 modules: imported constants are undefined at first; they become available later

I use ES6 modules in my JavaScript application. The sources are compiled with webpack and babel. This is a shortened version of the file that causes me trouble:

export const JUST_FORM = 0;
export const AS_PAGE = 1;

console.log(AS_PAGE); // **

export default function doSomething(mode = AS_PAGE) {
  console.log(mode);
  console.log(JUST_FORM);
}

I use this functionality just as you would expect.

import doSomething, { AS_PAGE } from './doSomething'

console.log(AS_PAGE);

doSomething();

When I run the app, it prints three times undefined and only once the expected value AS_PAGE which is the console.log marked with **. However, this is printed last! It shows that:

  • The AS_PAGE constant, when used as default parameter for the doSomething function`, is not defined at the moment of defining the function.
  • The JUST_FORM constant is not defined when doSomething is called.
  • The AS_PAGE constant is not defined when explicitly imported.

Apparently, what's happening here is that only the default export gets parsed and evaluated and the rest of the file is ignored until later. I import this file on several different places in my app (which is quite large at this moment) and at some point those values become actually available. Judging from the console output, it's matter of time, but it is possible that it has a different reason. Obviously, I do the importing exactly the same way in all places.

Anyway, I've written my whole application with the assumption that once I import something, it is immediately available and I can use it in my code. I read (briefly) about how ES6 modules should work and I haven't found anything that would prove this assumption wrong. And it has been working until now.

Also note, that the behavior is the same when I run it with webpack-dev-server or compile it to a single bundle.

Is this behavior really correct? What might be responsible for it?


As suggested in the comments, the answer here are circular dependencies.

There is actually no circular dependency present in the code provided in the question (because it's just a simplified snippet of code) but the symptoms are very clear.

The simplest example of a circular dependency is when file A imports file B and file B imports A. Unfortunately, what makes this issue hard to detect sometimes is that the circle can be arbitrarily large, spanning over a huge amount of files.

Circular dependencies are supported in ES6 and they can be used when one is careful enough. However, my takeaway here is that circular dependencies are very often a sign of bad design decisions. Which was exactly my case.