evanw / esbuild

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

Feature request: `__webpack_public_path__`-like variable #1441

Open arcanis opened 3 years ago

arcanis commented 3 years ago

Related: https://github.com/evanw/esbuild/issues/459

In Webpack, __webpack_public_path__ is a runtime variable that contains the public path used to load the assets. Unlike the static publicPath configuration (or what's --asset-path for esbuild), it can be freely modified by the program.

Having this variable is handy when the public path is expected to change from one environment to another (for example when the same assets are meant to be uploaded inside both an internal + external cdn). Without it, one has to rebuild the assets for each environment.

lukeed commented 3 years ago

Shouldn't this just be userland? I wouldn't want this, ever, for example. Might be as simple as:

globalThis.__webpack_public_path__ = ASSET_PATH;

With a define for ASSET_PATH

arcanis commented 3 years ago

I don't see how the code you mention would do anything unless ESBuild was generating code that uses __webpack_public_path__ (or whatever name it'd end up being) inside the urls path. For instance, it currently generates:

const pathToImg = './assets/icon-a456dfe.svg';

Since this path is static, it's impossible to change its public path at runtime. Instead, ESBuild would need to generate something like:

globalThis.__webpack_public_path__ = './assets';

const pathToImg = globalThis.__webpack_public_path__ + '/icon-a456dfe.svg';

The only other option would be for every single place that makes use of the imported images to add the public path themselves - which would go against the point of having a public path.

lukeed commented 3 years ago

Oh I see, sorry. I thought you were specifically asking for esbuild to output the webpack variable for compatibility purposes with imported code. Nvm :)

rtsao commented 3 years ago

This can be implemented as a plugin, but it feels a bit clunky. There may be a better way, but this is what I came up with:

let dynamicPublicPathPlugin = {
  name: "dynamic-public-path",
  setup(build) {
    build.onResolve({ filter: /\.txt/ }, (args) => {
      return {
        path: path.join(args.resolveDir, args.path),
        namespace:
          args.pluginData === "via-dynamic-file-loader"
            ? "file"
            : "dynamic-asset-path",
      };
    });

    build.onLoad(
      { filter: /\.*/, namespace: "dynamic-asset-path" },
      async (args) => {
        return {
          pluginData: "via-dynamic-file-loader",
          contents: `
          import assetPath from ${JSON.stringify(args.path)};
          const path = __webpack_public_path__ + assetPath;
          export default path;
        `,
          loader: "js",
        };
      }
    );
  },
};

A few thoughts:

{
  publicPath: '"http://example.com/v1"',
  // or 
  publicPath: '__webpack_public_path__',
}
evanw commented 3 years ago

Public paths can also end up in CSS files. This proposal essentially means esbuild would have to completely change its approach to CSS bundling from what it currently does (CSS goes in separate files) to something Webpack-like (CSS is generated at run-time using JavaScript) so that the run-time public path variable is respected. That's not a change that I'm planning to make, so this proposal could be considered out of scope.

arcanis commented 3 years ago

CSS is much less of a problem than JS. The reason why __webpack_public_path__ (or similar) is needed is to account for dynamic CDNs; in the case of JS, since the relative paths are resolved relative to the webpage, it's critical to have a way to "reroute" static asset paths - this is traditionally done by avoiding relative paths in JS bundles, and instead using an absolute public path.

By contrast, relative paths in CSS files are relative to the CSS file itself, so they'd already work even when switching from a CDN to another. Having __webpack_public_path__ wouldn't enable much that isn't already possible anyway.

giladv commented 3 years ago

just wanted to add that without this feature, for me, this great project is unusable. hopefully will be added soon

fregante commented 2 years ago

This proposal essentially means esbuild would have to completely change its approach to CSS bundling

Would it? To me it sounds like esbuild could use a base_path global that defaults to . and could be replaced by the user with esbuild --define:base_path=https://mycdn

arcanis commented 2 years ago

@evanw Is there any chance this could be reevaluated if scoped exclusively to the JS world (like Webpack)?

silverwind commented 2 years ago

Webpack-like (CSS is generated at run-time using JavaScript)

Normally, one would "extract" CSS in Webpack using mini-css-extract-plugin, which results in CSS loaded as CSS. I'm under the impression that a runtime-set __webpack_public_path__ also works for such extracted CSS. I think this is because the webpack runtime code performs loading of all resources in JS.

Thought I'm not totally sure whether a feature like __webpack_public_path__ is really necessary to have in esbuild. The main challenge is that some apps need to support changing the base URL at runtime without recompilation. For example changing the base URL from / to /sub/ should result in all resources being loaded from /sub/ thereafter, including splitted chunks.

For JS-initated loading, it should be possible to determine the correct path based on import.meta.url, but I think this would have to force all loading to go through JS to work and compiled CSS must not emit @import statements because those would go to the wrong URL.

giladv commented 2 years ago

another use case: when having chunks, you would like to have control over where the loaded from. for instance on a micro FE architecture bundles are loaded remotely and those bundles might have chunks. so unless the container app will be able to set the correct public url dynamically all chunks will try to load relative to the container app instead of relative to the micro app.

doberkofler commented 2 years ago

We have the same requirement for something like __webpack_public_path__ as we are using dynamic import for custom code splitting purposes and our base path for the JavaScript resources is dynamic.

doberkofler commented 2 years ago

This can be implemented as a plugin, but it feels a bit clunky. There may be a better way, but this is what I came up with:

@rtsao I'm having a hard time to fully understand your plugin and just wanted to ask, if you have successfully used your plugin approach before I dig into it?

Donorlin commented 1 year ago

any news about this? We as well need this feature pretty bad, because we know public path at runtime. Our paths are configured per environment.

Ketec commented 1 year ago

End up here on a similar issue - using __webpack_public_path__ to load static assets (images, json, worker scripts) in remotes in microfrontend setup. In this case the assets are available/served from the remote path and remote code needs to know what it is.

import.meta.url will not work here because it returns the shell/host domain that is serving that micro frontend.

amogower commented 1 year ago

➕ 1 on this 🙏

Being unable to set the public path at runtime means we have to break Rule III of the Twelve-Factor App and re-build our JS for each environment rather than having it simply adapt to the environment in which it's deployed.

SPWizard01 commented 10 months ago

This is the one thing that actually blocks me from migrating from webpack to esbuild my entire app so I had to do compromises and split it, for now :)

Would love to see it, my scenario is hosting app inside another app (i.e. SharePoint) which has many different site collections that are independent of each other and hold assets on their own(/sites/siteA, /site/anothersite) so I am pretty much stuck with webpack.

But I love the speed of esbuild, amazing. Plus this variable enables you to debug in production if you will, i.e. setting public path to your localhost :)

Iworb commented 6 months ago

I have the same issue with retrieving the public path for my microfrontends. Recently I've used Webpack to bundle the applications and server them on the different ports. __webpack_public_path__ helped the microfrontend to define where it should look for the assets to be loaded. Is there some workaround for the ESBuild to get the script path rather than the host application?

@doberkofler I think you had the same issue. Have you found the workaround? Because I don't believe it will be implemented. It's a big downside for ESBuild as for me.

SPWizard01 commented 6 months ago

The workaround is to use

import.meta.resolve

This would resolve relative to the module.

aleesaan commented 5 months ago

We wanted to migrate from webpack to esbuild but we're also blocked because of this issue, as we're using different CDNs in different environments. Is there any plan to add this feature @evanw? Or do you perhaps have suggestions about how this could be achieved currently?

vojtesaak commented 1 month ago

This issue blocks us migrating from webpack to esbuild as well :( Any news about this feature? @evanw 🙏🏻

ondrejpesat commented 1 month ago

We'd be in favor of this feature, too. 🙏