fal-works/esbuild-plugin-global-externals

How to allow all named imports from module without specifying them in namedExports

tim-hilt opened this issue · 4 comments

My esbuild-config looks like this:

import { eslintPlugin } from "esbuild-plugin-eslinter";
import { build } from "esbuild";
import { copy } from "esbuild-plugin-copy";
import { globalExternals } from "@fal-works/esbuild-plugin-global-externals";

const inputFiles = ["src/index.tsx"];

export const build = () => {
  const globals = {
    "styled-components": "Styled",
    react: "React",
    "react-dom": "ReactDOM",
  };

  build({
    entryPoints: inputFiles,
    outfile: "dist/index_ext.js",
    define: {
      global: "window",
    },
    bundle: true,
    minify: true,
    sourcemap: false,
    format: "esm",
    external: ["styled-components", "react", "react-dom"],
    plugins: [globalExternals(globals)],
  }).catch(() => process.exit(1));
};

and I do the following in a file:

import * as Styled from "styled-components";

window["Styled"] = Styled;
// Same thing for other libraries too

However I get

[ERROR] No matching export in "global-externals:styled-components" for import "css"

For each of the external libraries. I'm a bit lost in how I could proceed with debugging this. Did I misunderstand how the concept of global externals is actually working?

Thanks for your help!

I have done a little tinkering since I opened this issue. I found out, that the error goes away, when I list all named imports in the namedExports list.

I actually try to port over our build-pipeline from Rollup to esbuild. In the Rollup build, we're using output.globals and don't have to list the named imports.

So I guess the real question is: Can I allow all named imports without listing everything inside of namedExports?

I am hoping for the same feature, but it doesn't seem quite possible from the esbuild plugin API, which demands that the module content be generated without knowing anything about who's importing it.
Probably the best you can do is to write a script to scan your codebase and get all usages.

I created a namedExports function that introspects the module. Not sure if there are downsides, but it does seem to work.

import * as esbuild from "esbuild";
import * as React from "react";
import * as ReactDOM from "react-dom";

function namedExports(module) {
  return Object.keys(module).filter((x) => x !== "default");
}

import { globalExternals } from "@fal-works/esbuild-plugin-global-externals";

/** Mapping from module paths to global variables */
const globals = {
  react: {
    varName: "React",
    namedExports: namedExports(React),
    defaultExport: true,
  },
  "react-dom": {
    varName: "ReactDOM",
    namedExports: namedExports(ReactDOM),
    defaultExport: true,
  },
};

await esbuild.build({
  entryPoints: ["index.tsx"],
  bundle: true,
  outdir: "dist",
  minify: true,
  sourcemap: true,
  plugins: [globalExternals(globals)],
});

@jcheng5 Yeah, that definitely works. What you're doing here is simply list ALL exports regardless of whether they are actually imported somewhere. In 99.9% of the cases, there's no real downside about this other than that the generated code will be very slightly longer. The only case where it DOES have downside is when there are side effects reading the export from a module, but again this very rarely happens.