Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Import issue with circular re-exports #1894

Closed
DavidZbarsky-at opened this issue Dec 29, 2021 · 16 comments · Fixed by #2059
Closed

Import issue with circular re-exports #1894

DavidZbarsky-at opened this issue Dec 29, 2021 · 16 comments · Fixed by #2059

Comments

@DavidZbarsky-at
Copy link

Hi, I found another behavior change when upgrading esbuild from 0.13.5 to latest. To repro:

This is the smallest reduction I could get, everything else I tried removing doesn't trigger the change anymore. The code is a bit convoluted so perhaps it's tripping up the analyzer?

@mlrawlings
Copy link

We're seeing circular export issues in our project after upgrading as well. I did a test of the intermediate versions and the last working version is 0.14.3.

@DavidZbarsky-at
Copy link
Author

@evanw in case you had a chance to take a look, did the repro steps work for you?

@indutny-signal
Copy link

I second this, this is our repro: https://github.com/indutny-signal/esbuild-bug-repro

Running node main.js would print "hello world", while running node build/main.js would error.

@indutny-signal
Copy link

indutny-signal commented Feb 10, 2022

As @mlrawlings said the issue was introduced between 0.14.3 and 0.14.4 (even though it wasn't published), and it comes down to this generated code:

var a_exports = {};
__export(a_exports, {
  a1: () => a1,
  a2: () => a2
});
...
module.exports = __toCommonJS(b_exports);

vs previously generated

__export(exports, {
  a1: () => a1,
  a2: () => a2
});

I'll see if I can come up with a fix for this.


FWIW, the main difference between the top and bottom snippets is that exports is not updated in the top case and so circular requires see empty exports object, and the culprit is obviously #1849

@indutny-signal
Copy link

indutny-signal commented Feb 11, 2022

Now that I look more into it, it is reproducible with just two files:

import b from './b.js';

b.method();

and b.js

export default {
  method() {
    console.log('hello world');
  }
}

If compiled with --format=cjs and --platform=node - a.js won't run.

@hyrious
Copy link

hyrious commented Feb 11, 2022

FYI, Node.js has documented circular require() calls:
https://nodejs.org/api/modules.html#modules_cycles

In short, when circular require happens, any conflicting modules' incomplete exports will be returned. That's exactly what mentioned above because 0.14.5 moved module.exports = to the end of the file, which happens after circular require() taking place, results in the importer receiving {}.

The ideal solution for keeping esm circular import behavior correct in cjs context, should be always exporting names at the beginning of the file (like what previously did). It does not mean you won't get bugs any more, you can still reproduce the behavior like node.js docs said. In other words, there's no problem if all your input code is in esm.

Maybe related: #1737.

@indutny-signal
Copy link

@hyrious exactly, but also as mentioned here default exports are currently completely broken when running under node.

@indutny-signal
Copy link

In all honesty, I think it might be worth it to revert #1849 for the time being until better solution appears. What do you think @evanw ?

@hyrious
Copy link

hyrious commented Feb 11, 2022

In all honesty, I think it might be worth it to revert 1849 for the time being until better solution appears. What do you think evanw ?

I'm afraid not. That PR makes esbuild to align with other front-end bundlers' behaviors, which is adopted for a long time in the ecosystem. In fact, the node behavior is the new one.

@indutny-signal
Copy link

indutny-signal commented Feb 11, 2022

I'm afraid not. That PR makes esbuild to align with other front-end bundlers' behaviors, which is adopted for a long time in the ecosystem. In fact, the node behavior is the new one.

I'll check how babel/swc does it today, but I'm pretty sure that when output format is set to commonjs the expectation is that default export works 😁 Circular dependencies or not, you cannot currently import a default when compiling with esbuild without bundling.

@indutny-signal
Copy link

indutny-signal commented Feb 11, 2022

Here's the output from swc when compiling to commonJS:

b.js

Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.default = void 0;
var _default = {
    method: function method() {
        console.log('hello world');
    }
};
exports.default = _default;

a.js

var _bJs = _interopRequireDefault(require("./b.js"));
function _interopRequireDefault(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
_bJs.default.method();

@indutny-signal
Copy link

Babel does it similarly.

@indutny
Copy link
Contributor

indutny commented Feb 25, 2022

I take my comments back about default exports. I see how esbuild behaves now and more importantly why it is done this way.

The circular imports on other hand still needs to be fixed. I think the fix could be as easy as dropping:

var exports = {};

and just using exports provided by node when --platform=node is used.

indutny added a commit to indutny/esbuild that referenced this issue Feb 26, 2022
When using cjs format make sure to move `module.exports` to the top of
the generated chunk, right after the `__export()` call:

    var exports = {};
    __export(exports, {
      // ...
    });
    module.exports = __toCommonJS(exports);

This guarantees that all circular requires following these calls would
see the correct `module.exports` object.

Additionally, add an extra argument for `__reExport` to copy the data to
the `module.exports`:

    __reExport(exports, module.exports, ...)

This is required to support `export * from '..'` since now
`module.exports` wouldn't automatically receive properties otherwise.

Fix: evanw#1894
@indutny
Copy link
Contributor

indutny commented Feb 26, 2022

Opened a PR to fix this #2059

@indutny-signal
Copy link

Just a quick heads up. Y'all should try updating to 0.14.27 which was just released!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants