The ECMAScript module system allows modules to cyclically depend on each other. However, modules must still be initialized one at a time. Normally, a module is initialized after its imported modules, but in the case of cyclic imports, this is not possible. This means that some modules in a cycle must be initialized before all of its imported modules are ready to be used.

When the top-level code of a library depends on a value obtained from a cyclic import, its behavior thus depends on the initialization order. This order is determined by which module is first imported from the main module. Simply adding, removing, or reordering import statements can therefore cause the cyclic modules to stop working.

Note that imports that are only used for type annotations in TypeScript files are removed at compile time. Such imports can therefore safely be used in a cycle.

Cyclic dependencies can be hard to break. There are several approaches that may help:

In the example below, services.js and audio.js both depend on each other. As long as services.js is imported first, the code works as expected, but if audio.js is imported first, the list of services will contain undefined instead of the AudioService class.

One solution is to factor out the registerService function into another module which AudioService can safely depend on. The services module can use a re-export to maintain its original interface:

  • Mozilla Developer Network: Import statement.