Runtime error in case of circular dependency between ES Modules with a re-export
See original GitHub issueBug report
I’m seeing a runtime error in a cyclic dependency case where:
- module A imports and re-exports a reference to a function from module C and also imports module B
- module B depends on the function from module C via module A’s re-export of it
In the minimal reproduction in https://github.com/bregenspan/webpack-circular-import-issue, this manifests as the runtime error: “Uncaught TypeError: n is not a function”.
This issue seems different from https://github.com/webpack/webpack/issues/9060, as it involves an exported function declaration and so as I understand it shouldn’t involve the temporal dead zone in the same way; also Rollup and native ES module support in Chrome and Node.js execute the code as expected, unlike in that case.
What is the current behavior?
In the example in https://github.com/bregenspan/webpack-circular-import-issue, there is a runtime error with the message: Uncaught TypeError: n is not a function when the bundled code is run in a browser. This is because the definition of the default export of the “shared.js” module used in this example is bundled following the first reference to it.
If the current behavior is a bug, please provide the steps to reproduce.
Clone the minimal reproduction in https://github.com/bregenspan/webpack-circular-import-issue, run npm install && npm run start. You should now see a browser window and can click “webpack.html” and open your browser’s developer console to see the noted behavior.
What is the expected behavior?
There should be no runtime error, and the console output in the browser should match that shown in the other examples (rollup.html and native.html):
shared import
"shared import" should be logged twice, once above this log line, once below
shared import
While Rollup operates differently from Webpack, I would expect the outputted code in the bundle here to be functionally similar, such that the function declaration imported from the shared import is hoisted and accessible in both of the modules that reference it. (See example of Rollup’s output in dist/rollup/bundle.js in the repo linked above after running npm run build).
Other relevant information:
webpack version: 4.43.0 / 5.0.0-beta.16 Node.js version: v12.16.3 Operating System: MacOS 10.15.4 Additional tools: Chrome 81.0.4044.138
Other notes:
- Reordering imports like this: https://github.com/bregenspan/webpack-circular-import-issue/commit/c4a9f00e1e2b5a9aefa84f3bf526d2f88429f305 works around the issue, but I did not expect this to be necessary.
- This is a somewhat strange pathological case, but I did encounter it in the wild in a production application (after upgrading Babel, which had apparently been masking the issue in this particular code). I definitely intend to rewrite the relevant code to not have this cyclic dependency for readability and maintainability reasons, but it seems like a case where the code as written should work as expected and not produce a runtime error.
- If this behavior is to spec and is a wontfix, it could be nice if there was a warning in this case, possibly by including something like circular-dependency-plugin by default.
Issue Analytics
- State:
- Created 3 years ago
- Comments:5 (3 by maintainers)

Top Related StackOverflow Question
I had some ideas to detect circular dependencies and improve optimization based on them. But that got a bit stuck as there are dirty ways to hide them so detection would not be relieable enough:
The module graph is non-circular, but execution is circular at runtime.
So this gets pretty tricky. Probably only possible for ESM. Still tricky when interacting with CJS or other dynamic things.
hmm… seems like you hit a limitation here. In certain edge cases the way webpack generates code doesn’t allow hoisting function correctly in circular dependencies. In your case you are executing a function of a module before runtime sees the import to that module (before evaluation of the module has started). The ESM spec allows that.
I guess this code would work in production due to module concatenation, which handles hoisting of function better.
As workaround you should be able to switch the order of imports to fix that.
In practice this edge case is pretty rare and in all the years since ESM integration in webpack exists it has not be reported before. It’s rare because most code like this would break anyway since you would not be able to access any variable of the scope of the shared module, since they are all still in TDZ or undefined.