question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Runtime error in case of circular dependency between ES Modules with a re-export

See original GitHub issue

Bug 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:closed
  • Created 3 years ago
  • Comments:5 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
sokracommented, May 12, 2020

Has there been thought to including something like circular-dependency-plugin by default? I think it could help head off these kinds of issues by catching circular dependencies early and strongly recommending against them / encouraging a pattern of whitelisting exceptions for the few cases they’re needed. I like that Rollup warns on this circular dependency case. But maybe that’s too opinionated to include in the core?

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:

// index.js
require("./a");

export function innocentFunction() {
  // lazy imported
  require("./a");
}

// a.js
const b = require("./b");

// b.js
const { innocentFunction } = require("./index.js");
innocentFunction();

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.

2reactions
sokracommented, May 12, 2020

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.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to fix this ES6 module circular dependency?
I have three modules A , B , and C . A and B import the default export from module C , and...
Read more >
Don't make the same mistake I did. Avoid circular ... - Reddit
My two pieces of advice for avoiding circular dependencies: Don't re-export stuff (unless you're publishing a library, in which case your top ...
Read more >
How to solve this basic ES6-module circular dependency ...
For reference, I'm trying this in Meteor, in which case the first module to be evaluated is module B, but that means that...
Read more >
Modules | NestJS - A progressive Node.js framework
Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines ...
Read more >
NgModule FAQ - Angular
You can import it directly or from another NgModule that re-exports it. ... NgModules with circular references, so don't let Module 'A' import...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found