How can an es6 module import itself?
Asked Answered
T

1

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?

Tripartition answered 25/10, 2016 at 12:59 Comment(7)
what you want to do ?Alcoholicity
Imports aren't imperative and resolution and execution are separate. The imports are parsed and resolved first. By the time the code executes, all bindings are already resolved.Rugby
the best practice is to not do thisGaylordgaylussac
@anshu I'm just curious.Tripartition
@TheReason Actually, importing your own module namespace object can be a useful thing that helps to avoid some duplicate code.Contexture
@Contexture Interesting... Have an example of that?Shugart
@Shugart If you need to lookup things by name, you typically use an object literal with the respective values as properties. If those happen to be exactly your exports, you can just use the module namespace object.Contexture
C
20

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 :-)

Contexture answered 25/10, 2016 at 14:9 Comment(6)
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?Searles
@Searles 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 (when for some reason it's not possible to just name it).Contexture
I'm curious, will there be a performance penalty for this self-to-self circular importing when using webpack/vite?Scout
@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 notContexture
Thanks for the quick response, from my experience, circular references can often cause some problems, so I'm quite cautious about this.Scout
@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.Contexture

© 2022 - 2024 — McMap. All rights reserved.