16

I have a module called fooModule. Inside this module, I import fooModule (itself):

import * as fooModule from './fooModule';

export function logFoo() {
  console.log(fooModule)
}

When logFoo() is called, I can see all of the exports of the fooModule. How does this work?

Mr. Spice
  • 1,552
  • 1
  • 15
  • 15

1 Answers1

19

Circular dependencies are no problem for declarative imports/exports. In your case, the circle is of minimal length though :-)

The solution is that an import does not import a value into a variable, but that it makes a variable a reference to the exported variable. Have a look here for an example of a mutable variable, and at this question for exact terminology.
And it's the same for module namespace objects - their properties are just getters that resolve to the actual exported variable.

So when your module is loaded and evaluated, the following steps occur:

  1. The source is statically analysed for export and import declarations to build a dependency graph
  2. The module scope is created
  3. Since the only dependency of your module is itself, and that already is getting initialised, it doesn't need to wait for it
  4. The fooModule variable is created and instantiated to an object with the exported names of the module, which are known to be ["logFoo"]. The fooModule.logFoo property becomes a getter that will evaluate to the logFoo variable in the module scope (if you had used export {A as B}, then fooModule.B would resolve to A, but in your case both names are the same).
  5. The variable declarations in the module scope create the variables, in your case logFoo, and function declarations are initialised (i.e. logFoo gets assigned the function)
  6. The module code is run (in your case, nothing happens)

Now when you call logFoo in a module that imports it, fooModule will refer to the namespace that contains logFoo. No magic :-)

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I fully appreciate all the time you put into SO, but I'm failing to understand a use-case for this behavior. You mentioned above this can be used to avoid duplicate code - but when does it semantically make sense for something to depend on itself? – aaaaaa May 17 '17 at 19:39
  • 3
    @aaaaaa I can only think of two use cases: getting one's own module namespace object (for dynamic property access, for default-exporting it, etc - avoiding to construct such an object manually), and accessing one's own [anonymous default export](http://stackoverflow.com/a/35225936/1048572) (when for some reason it's not possible to just name it). – Bergi May 17 '17 at 22:18
  • I'm curious, will there be a performance penalty for this self-to-self circular importing when using webpack/vite? – Wenfang Du Feb 26 '23 at 11:33
  • 1
    @WenfangDu I wouldn't expect any, the bundler should do exactly the same thing for all kinds of module imports, regardless whether there is a circular dependency or not – Bergi Feb 26 '23 at 11:36
  • Thanks for the quick response, from my experience, circular references can often cause [some problems](https://spin.atomicobject.com/2018/06/25/circular-dependencies-javascript/), so I'm quite cautious about this. – Wenfang Du Feb 26 '23 at 11:45
  • 1
    @WenfangDu I'm not saying that circular dependencies don't cause problems, and can only endorse the "*Careful planning is required to allow cyclic module dependencies to work correctly within an application.*" bit that your article quotes. But there should be no performance impact, and from a self-import there will also be no trouble with non-obvious temporal dead zones. – Bergi Feb 26 '23 at 11:51