evanw / esbuild

An extremely fast bundler for the web
https://esbuild.github.io/
MIT License
38.21k stars 1.15k forks source link

Allow plugins to know which entry point they are resolving for #3946

Closed RDIL closed 1 month ago

RDIL commented 1 month ago

Hi!

I'm trying to migrate a codebase to esbuild, and I really want to be able to call build just once, but I can't currently.

In short, the entire codebase (written in React) has certain components that differ between mobile and desktop. The way we're currently loading them is by altering resolveExtensions to first check for Component.${platform}.jsx, then Component.jsx. This is great, except for the fact that resolveExtensions can't be dynamic. So, right now, we have to do a for loop over mobile and desktop and call build for each of them.

Ideally, we would love to use the same build call for both mobile and desktop, but because resolveExtensions isn't dynamic, and because plugins can't tell which entrypoint they have been called from, this currently isn't possible.

evanw commented 1 month ago

Sorry, I don't understand. Can you give a specific example?

plugins can't tell which entrypoint they have been called from

On-resolve plugins get an importer argument which is the path of the module containing the import to be resolved. That could be interpreted as the "entrypoint they have been called from." Is that what you're looking for?

I really want to be able to call build just once

Why is that? If you're doing separate builds for mobile and desktop, it's typically more natural to do a separate build for each one with different build settings instead of trying to get one build that works for both.

RDIL commented 1 month ago

Imagine I have:

entryPoints: {
    "a-desktop": "a.js"
}

If a.js imports b.js, and b.js imports c.js, then when resolving c.js, my plugin needs to be able to know that it's resolving it on behalf of the a-desktop entrypoint.

If this ability was added, then I could do something along the lines of (pseudocode):

function getPlatformFromEntryPoint(ep) {
    // pretend there is logic to do string manipulation here to grab the platform
    return platform
}

onResolve(({ path, entrypoint }) => {
    if (exists `${path}.${getPlatformFromEntryPoint()}.jsx`) {
        return that file
    } else if exists without extension {
        return that file
    }
})

I really want to be able to call build just once

Because I'd like to use the serve api on a single port. It feels wrong to create a custom server just for this use case.

evanw commented 1 month ago

If a.js imports b.js, and b.js imports c.js, then when resolving c.js, my plugin needs to be able to know that it's resolving it on behalf of the a-desktop entrypoint.

This is not really possible. There is no one entry point for a given module because any module can be transitively imported from any entry point (imported modules are shared between entry points within a single build context). In addition, during the build (which is when these plugins are run), it's not possible to provide all of the entry points that a given module is transitively imported from because that information is only known at the end of the build. This is because during the build, the module graph is still in the middle of being traversed and more graph edges may be discovered later on before the build ends. The build also processes modules in parallel so the partially-constructed module graph wouldn't even be deterministic if it were to be exposed to plugins (which it's not).

I really want to be able to call build just once

Because I'd like to use the serve api on a single port. It feels wrong to create a custom server just for this use case.

Unfortunately the serve API, like the build API, is only designed to work with a single set of build options. Using a custom server is the most straightforward way to do this with esbuild I think. FWIW creating custom proxy server in node isn't that much code. There's an example in the documentation: https://esbuild.github.io/api/#serve-proxy

RDIL commented 1 month ago

Makes sense.