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

Question: How to recursively process files but not produce a single bundle #3064

Closed
znewsham opened this issue Apr 16, 2023 · 6 comments
Closed

Comments

@znewsham
Copy link

I have a large application I want to process with esbuild - the code is mostly JS with a smattering of TS. Mostly the code is compliant with the version of node (14) I'm running, though there are a handful of places that use features not available in node 14 that need converting.

My goal is to pass in an entry point (e.g., server/main.js) to esbuild - and have it process the files as necessary and copy them to the output directory.

Unfortunately, this doesn't happen - if I pass the entry point to esbuild, only that one file appears in the output directory and obviously if I pass in bundle: true, I end up with a bundle.

Up until now I've been using bundle: true - with a custom plugin that recursively calls build for each imported file as a new entry point - unfortunately the problem with this approach is it down-levels certain features that shouldn't be (e.g., private class members) specifically because of the bundle flag (even though it's a 1:1 mapping of input to output).

How can I achieve the desired result?

@hyrious
Copy link

hyrious commented Apr 16, 2023

There are a few different issues involved in your problem:

  • Custom (manual) chunks, or "preserve modules". Basically you want more control over the output files structure. This is not supported yet, and "code splitting" is still experimental because of this.

    The best way for now is manually bundle each output carefully like what you've already done.

  • Skip some behaviors in bundle mode. esbuild always lowers top level class static fields in bundle mode, and then it will lower private fields too for evaluation order problems. It has a workaround that you can put static fields outside of a class:

    class A { #b = 1 }
    A.c = 2  // put it here

    On the other hand, you can also use other tools like babel to transform (lower) your code first before feeding it to esbuild.

@znewsham
Copy link
Author

znewsham commented Apr 16, 2023

Thanks for the quick reply!

If you can point me to where this behaviour (the lowering of top level class static fields) is enforced, I'd be reasonably happy if the solution was for me to fork it and either:

  • just disable that behaviour or
  • stick it behind a flag

Separately - and just out of interest - what is special about top level class static fields that triggers this behaviour? I also think (based on other reported issues) that I'll get tripped up with the class referencing itself within the class body

@hyrious
Copy link

hyrious commented Apr 17, 2023

point me to where this behaviour enforced

Here you are:

// Safari workaround: Automatically avoid TDZ issues when bundling
result.avoidTDZ = p.options.mode == config.ModeBundle && p.currentScope.Parent == nil

Summary: when bundle: true, top level class static fields are always lowered. Related issues: #1524 #2416 #2800 #2950

what is special about top level class static fields that triggers this behaviour

This is because esbuild always transforms class declarations to variable declarations in bundle mode:

class A { static a = new A() }
// becomes
var A = class { static a = new A() } // error: not found 'A'

In the second line, the reference to class name 'A' will become undefined and it causes problems. The same issue also occurs in the sub-new syntax "class static blocks".

@znewsham
Copy link
Author

This is because esbuild always transforms class declarations to variable declarations in bundle mode

Why? Something to do with duplicate class names?

@evanw evanw added the classes label Apr 17, 2023
@josundt
Copy link

josundt commented Apr 22, 2023

I'm looking for the same feature as @znewsham.

Today the bundle parameter/BuildOption implies two things:

  1. Resolve the import/require module dependency graph for each entry point.
  2. For each unique file across all dependency graphs, transpile then bundle into a single file.

Today, if you don't want bundling you need to add every single file as entry points and set bundle to false.

It would be very nice to have an alternative to bundle f.ex. resolve (both cannot be true at the same time).
resolve would imply:

  1. Resolve the import/require module dependency graph for each entry point (same as bullet 1 above).
  2. For each unique file across all dependency graphs, transpile then write to separate files mirroring the source (folder/module structure).

Such a feature would be very valuable for me and apparently others as well.
It would include source tree-shaking even when not bundling, ensuring dead source modules are not included when writing to the output folder.

In the build script for my current project, I have made my own dependency graph resolver so that I can add each of the resolved modules as esbuild entry points. I still think it would be beneficial if this was added as a native feature of esbuild.

If this feature is added, it would be nice if the BuildResult.outputFiles in the API would contain path to all written files.

@evanw
Copy link
Owner

evanw commented Jun 12, 2023

I'm closing this issue because it duplicates other open issues. The request about bundling with recursive file processing is a duplicate of #708, and the request to not lower private class fields is a duplicate of #1328. You can read #2889 for more information about why top-level classes are transformed this way during bundling. FWIW I'm hoping to improve this transformation to avoid lowering private class fields at some point.

@evanw evanw closed this as not planned Won't fix, can't repro, duplicate, stale Jun 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants