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

Using globally defined external dependencies with named and default exports #2668

Closed
tim-hilt opened this issue Nov 10, 2022 · 5 comments
Closed

Comments

@tim-hilt
Copy link

Kind of related to #337 - We used Rollup before but I'm currently busy porting everything over to use esbuild.

One thing I struggeled with is the use of external dependencies, that are present on the window-object like so:

import * as Styled from "styled-components";
import * as React from "react";
import * as ReactDOM from "react-dom";

window["Styled"] = Styled;
window["React"] = React;
window["ReactDOM"] = ReactDOM;

I have prepared my esbuild-config.js to account for the global-externals like so:

  build({
    define: {
      global: "window",
    },
    external: ["styled-components", "react", "react-dom"],
    // ...
  }).catch(() => process.exit(1));

Now I need a plugin to tell esbuild to include the variables on window in the build. I tried out fal-works/esbuild-plugin-global-externals and a-b-r-o-w-n/esbuild-plugin-globals. The former is more configurable and seems to be better suited for esm-builds which I intend to use like so:

import { build } from "esbuild";
import { globalExternals } from "@fal-works/esbuild-plugin-global-externals";
import * as React from "react";
import * as Styled from "styled-components";
import * as ReactDOM from "react-dom";

  const globals = {
    "styled-components": {
      varName: "Styled",
      namedExports: Object.keys(Styled).filter((key) => key !== "default"),
    },
    react: {
      varName: "React",
      namedExports: Object.keys(React).filter((key) => key !== "default"),
    },
    "react-dom": {
      varName: "ReactDOM",
      namedExports: Object.keys(ReactDOM).filter((key) => key !== "default"),
    },
  };

  build({
    // ...
    define: {
      global: "window",
    },
    /// ...
    format: "esm",
    external: ["styled-components", "react", "react-dom"],
    plugins: [globalExternals(globals)],
  }).catch(() => process.exit(1));
};

The application builds successfully, but now when I try to open one part of the application that relies on the global externals being available, I see this in the browsers console:

Uncaught TypeError: import_styled_components2.default.div is not a function

When I take a look at it in the debugger, I see that div is actually inside of import_styled_components2.default.default. So the plugin didn't resolve the default export correctly. Both of the libraries suffer from this problem; default exports are not usable for me.

From my point of view this question would be better handled in a community forum or on the repos of the plugins themselves, but it's been more than a year that they received new commits. An issue that I created there didn't receive any answers.

Can you help me get this to work or point me to a place that's better suited, if this doesn't fit the issues here?

@evanw
Copy link
Owner

evanw commented Nov 10, 2022

From my point of view this question would be better handled in a community forum or on the repos of the plugins themselves, but it's been more than a year that they received new commits. An issue that I created there didn't receive any answers.

Can you help me get this to work or point me to a place that's better suited, if this doesn't fit the issues here?

I'm not going to comment on or investigate plugins written by others. If it's been abandoned then you may not want to use it.

Keep in mind that import * as foo from and import foo from do different things. Doing import foo from is the exact same thing as import { default as foo } from. So if the plugin generates something like this:

let obj = { default: 'something' }
export default obj

and you import it using import * as foo from, then you will end up needing to do something = foo.default.default. If you instead import it using import foo from (or import { default as foo } from) then you will only need to do something = foo.default. So if you are willing to change your imports from import * as foo from to import foo from then the plugin you're using might start working (assuming it's using export default). This approach has the benefit of being maximally efficient because it doesn't generate any shims.

If you really want to keep importing things using the import * as foo from syntax, then you may have better luck writing a plugin that generates a CommonJS module instead of an ES module (e.g. module.exports = window.something). You could try using this example plugin as a starting point. This approach has the disadvantage of needing to go through the CommonJS-to-ESM conversion process at run-time, but it may end up getting something.default to show up as the default property on the imported module namespace object (I say "may" because the default export has compatibility issues in JavaScript and the presence of the __esModule marker changes how it works).

@tim-hilt
Copy link
Author

I'm already taking some time to try stuff out and evaluate. In this case it seems that the plugin is at fault, as it doesn't correctly treat default-exports. I'll report back when I have more questions or get stuff working. I actually hoped it would work as Rollups output.globals-field, but I think it does something else.

In the meantime another thing: Do you know if star-imports are tree-shakable?

@evanw
Copy link
Owner

evanw commented Nov 10, 2022

Do you know if star-imports are tree-shakable?

Usually, yes. Caveats:

  • Both files must be in the bundle
  • Both files must use ESM
  • The importing file must not access properties using any kind of indirection

If those conditions are met, then esbuild's bundler will automatically convert this:

import * as foo from 'foo'
foo.bar()

into this during the bundling operation:

import { bar } from 'foo'
bar()

@tim-hilt
Copy link
Author

Thanks for the response! We might have some stuff in our codebase, that we can optimize then.

@evanw
Copy link
Owner

evanw commented Dec 3, 2022

Closing this issue as this conversation did not receive additional replies.

@evanw evanw closed this as not planned Won't fix, can't repro, duplicate, stale Dec 3, 2022
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