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

esbuild selecting incorrect files when bundling #2420

Closed
TestSubject06 opened this issue Jul 27, 2022 · 8 comments
Closed

esbuild selecting incorrect files when bundling #2420

TestSubject06 opened this issue Jul 27, 2022 · 8 comments

Comments

@TestSubject06
Copy link

TestSubject06 commented Jul 27, 2022

I'm not sure I'll be able to fully articulate the issue here, and I can't produce a minimal failure case.

I have a 1st party dependency which then itself depends on d3 to produce bar charts. I can verify that the dependencies are present in the node_modules directory all fine. But when I build the application with a platform of browser, esbuild complains that the library xmlhttprequest cannot find fs, http, https, or child-process. The source of the file requiring xmlhttprequest is coming from d3-request.node.js.

The issue is that file is supposed to only be for node projects and should not be imported into browser projects. Browser projects should import d3-request.js instead.

My first party library is importing from just d3 and grabbing what it needs from the forwarded exports found there, which also never refer to the .node variants.

My question then is how can I stop esbuild from importing the node variants of these libraries?

@TestSubject06 TestSubject06 changed the title rebuild selecting incorrect files when bundling esbuild selecting incorrect files when bundling Jul 27, 2022
@evanw
Copy link
Owner

evanw commented Jul 27, 2022

You can use --log-level=debug or even --log-level=verbose to get more detail about esbuild's path resolution decisions. From there you should be able to figure out why this is happening.

@zstarvit
Copy link

How can I pipe the output of verbose to a file? It seems to attach itself to the terminal regardless of any pipe or std redirect. I don't see the root cause under log level debug, but verbose pushes so fast I can't possibly see anything, and my history just dies immediately. I can't | grep or > test.log because it seems to just bypass all of it.

@evanw
Copy link
Owner

evanw commented Jul 27, 2022

Log output goes to stderr.

@zstarvit
Copy link

Was just about to come mention that.

@zstarvit
Copy link

zstarvit commented Jul 27, 2022

    Attempting to load "/home/zstarvit/workspace/ui-tsttravel/node_modules/d3" as a file
      Checking for file "d3"
      Checking for file "d3.tsx"
      Checking for file "d3.ts"
      Checking for file "d3.jsx"
      Checking for file "d3.js"
      Checking for file "d3.css"
      Checking for file "d3.json"
      Failed to find file "d3"
    Attempting to load "/home/zstarvit/workspace/ui-tsttravel/node_modules/d3" as a directory
      Read 13 entries for directory "/home/zstarvit/workspace/ui-tsttravel/node_modules/d3"
      Searching for main fields in "/home/zstarvit/workspace/ui-tsttravel/node_modules/d3/package.json"
        Did not find main field "browser"
        Found main field "module" with path "index"
          No "browser" map found in directory "/home/zstarvit/workspace/ui-tsttravel/node_modules/d3"
          Attempting to load "/home/zstarvit/workspace/ui-tsttravel/node_modules/d3/index" as a file
            Checking for file "index"
            Checking for file "index.tsx"
            Checking for file "index.ts"
            Checking for file "index.jsx"
            Checking for file "index.js"
            Found file "index.js"
        Found main field "main" with path "build/d3.node.js"
          No "browser" map found in directory "/home/zstarvit/workspace/ui-tsttravel/node_modules/d3"
          Attempting to load "/home/zstarvit/workspace/ui-tsttravel/node_modules/d3/build/d3.node.js" as a file
            Checking for file "d3.node.js"
            Found file "d3.node.js"
        Resolved to "/home/zstarvit/workspace/ui-tsttravel/node_modules/d3/build/d3.node.js" because of "require"

So it looks like it has to do with the main field in the package.json telling it to use the node versions instead of the browser versions. It found the index.js, but then also found the build/d3.node.js and decided to use the node variant because of "require" whatever that is or means.

Am I meant to change the order of things somewhere?

@evanw
Copy link
Owner

evanw commented Jul 27, 2022

d3@6/package.json looks like this:

{
  "main": "dist/d3.node.js",
  "module": "index.js",
  ...
}

Nothing appears to provide any browser-specific files. This is a problem with the package, not with esbuild. Package authors should not be using module to mean "browser". All it means is ESM (instead of CommonJS). Instead they should be using the browser field to specify browser-specific entry points. This is documented here: https://esbuild.github.io/api/#main-fields. Specifically this part:

For package authors: If you want to author a package that uses the browser field in combination with the module field to fill out all four entries in the full CommonJS-vs-ESM and browser-vs-node compatibility matrix, you want to use the expanded form of the browser field that is a map instead of just a string:

{
  "main": "./node-cjs.js",
  "module": "./node-esm.js",
  "browser": {
    "./node-cjs.js": "./browser-cjs.js",
    "./node-esm.js": "./browser-esm.js"
  }
}

If you want to, you can attempt to work around this problematic package by configuring e.g. --main-fields=browser,module,main. By default esbuild will select main over module for packages imported via require(). This is important for CommonJS compatibility regarding default exports (see e.g. #363). Explicitly setting --main-fields= disables this behavior (but may cause issues with some packages). That may fix your problem.

In any case, I'm closing this issue because it looks like esbuild is working as intended.

@evanw evanw closed this as completed Jul 27, 2022
@zstarvit
Copy link

zstarvit commented Jul 28, 2022

Just wanted to pop in and say that --main-fields=browser,module,main was perfect for my use case. It got my bundle up and running and the application is now working in the browser. We've got a lot of testing to do to make sure all the features work as we expect, but just as a simple happy path, the page is running with all the components present.

Thanks very much for your help, and for the incredibly fast build product - it's insane.

Edit for those who come after: This solution is not a good path forward. The bundle compiles, and passes type checking just fine, but produces errors that are not known until runtime. Even in the best of circumstances, having errors be hidden until later in the pipeline is unacceptable as a compromise.

I have a couple of options for moving forward:

  1. Get d3 to fix their package.json (unbelievably unlikely)
  2. Write a plugin to change d3 module imports to unambiguous references to the browser versions of the files.
  3. I remember seeing something about module resolution detecting whether a dependency is both required and imported that esbuild would treat the module resolution differently. So I could possibly import the d3 files in an empty file and that might work too, but we'd have to disable eslint rules on them.

@zstarvit
Copy link

For those that come after me, know that the regex engine used by esbuild is not the JS regex parser, but the GoLang regex parser. This means that it does not support negative lookahead structures.

So if like me you want to rewrite d3-request into d3-request/index then you'll have to use some crazy regex shenanigans to omit the "/index" match, or you'll end up in an infinite loop.

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

3 participants