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

Cannot access X before initialization #542

Closed
arcanis opened this issue Nov 17, 2020 · 2 comments
Closed

Cannot access X before initialization #542

arcanis opened this issue Nov 17, 2020 · 2 comments

Comments

@arcanis
Copy link

arcanis commented Nov 17, 2020

Might be an unfortunate side effect, and it's probably something that can be workaround with some efforts, but I figured it's at least worth documenting:

index.js

import {Test} from './lib';

export function fn() {
    return 42;
}

export const foo = [Test];

lib.js

import {fn} from './index';

export class Test {
    method() {
        fn();
    }
}

Command line

yarn esbuild --bundle index.ts | node

What happens

Basically the cyclic dependency seems to confuse ESBuild into generating something like this:

export function fn() {
    return 42;
}

export const foo = [Test];

class Test {
    method() {
        fn();
    }
}

This causes the generated output to fail since Test isn't defined before being used in the foo definition. In theory this code is (weird but) expected to work. Two reasons for that:

  • fn is declared via a regular function declaration it seems to be "immediately exported" before anything else is imported (so doing a console.log(fn) in the top-level of lib.js would work).

  • even if fn was defined with an expression (export const fn = ...), it would still work because lib.js doesn't actually use it at the top-level, and the symbol would have a live binding that would make it available once defined (however adding console.log(fn) at the top-level wouldn't be possible).

@evanw
Copy link
Owner

evanw commented Nov 18, 2020

I believe this has to do with ESM-to-CJS conversion. If you bundle that with the esm format you get this, which should work correctly:

// lib.js
class Test {
  method() {
    fn();
  }
}

// index.js
function fn() {
  return 42;
}
const foo = [Test];
export {
  fn,
  foo
};

Otherwise you will get this, which doesn't work correctly:

// index.js
var require_index = __commonJS((exports) => {
  __export(exports, {
    fn: () => fn2,
    foo: () => foo
  });
  function fn2() {
    return 42;
  }
  const foo = [Test];
});

// lib.js
const index = __toModule(require_index());
class Test {
  method() {
    index.fn();
  }
}
module.exports = require_index();

Can you confirm that the second case is what's actually happening here? I can't reproduce this code you wrote with any esbuild settings:

export function fn() {
    return 42;
}

export const foo = [Test];

class Test {
    method() {
        fn();
    }
}

@arcanis
Copy link
Author

arcanis commented Nov 20, 2020

Can you confirm that the second case is what's actually happening here? I can't reproduce this code you wrote with any esbuild settings:

Yep, that's it - I didn't paste it as-is since it seems fairly large, so I just made a rough example.

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

No branches or pull requests

2 participants