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
feat: Expose a way to set publicPath
in remoteEntry
#10703
feat: Expose a way to set publicPath
in remoteEntry
#10703
Conversation
Expose a way to set `publicPath` in `remoteEntry`
For maintainers only:
|
Thank you for your pull request! The most important CI builds succeeded, we’ll review the pull request soon. |
@sokra we all good to merge this? |
@evilebottnawi @sokra Could you please review this PR? 🙂 |
@NMinhNguyen Could you explain why you need this feature? I'm a bit on the fence with this feature and don't think we need it. There are a bunch of approaches how publicPath can be handled: Static publicPathThe most common approach is probably to provide a constant publicPath via publicPath inferred from scriptOne could use A configuration for this could be: entry: {
remote: "./setup-public-path"
},
plugins: [
new ModuleFederationPlugin({
name: "remote", // the same as the entry
// ...
})
] // setup-public-path.js
__webpack_public_path__ = document.currentScript.src + "/../"; offer an host API to set the publicPathYou can expose a module to allow the host to set the public path. Note that chunk loading also uses the publicPath so it's important that the module is placed in the entry chunk. A configuration for this could be: entry: {
remote: "./public-path"
},
plugins: [
new ModuleFederationPlugin({
name: "remote", // the same as the entry
exposes: ["./public-path"]
// ...
})
] // public-path.js
export function set(value) { __webpack_public_path__ = value; } // in the host
const publicPath = await import("remote/public-path");
publicPath.set("/whatever");
// bootstrap app Note that no other remote module must be loaded before publicPath is set. |
@sokra thanks very much for your reply! I think your last solution is the one that would benefit us the most - I hadn't considered exposing another remote module just for this - I tried a named export from the same remote module, but yeah it was kind of too late.
So we have a host application that mounts child applications at const remoteEntry = window[moduleIdentifier]; // e.g. window['foo']
remoteEntry.setOptions({ publicPath: '/app/foo' });
// Based on https://twitter.com/ScriptedAlchemy/status/1248732087600832512
// because these apps aren't known at build time but are read from a database
const App = React.lazy(() => remoteEntry.get('App').then(factory =>
factory()
));
// elsewhere
<App /> Hope this was clear. But yeah, I think your last option would help us here. |
Same from my side, imagine a tool like Grafana where we install things dynamically and we need to route the |
I've been working with @NMinhNguyen, too.
We're in an enterprise environment in which many applications are on different domains and aren't able to control their CORS headers. They'd otherwise be inaccessible but we can route them through a proxy to gain access. So for example we can make This fails because when the script tag is loaded, as by default the We could hard-code this as something else (relative or absolute URL), but then the applications' could no longer be loaded standalone via their deployed location. Therefore, we want a way of dynamically altering the I know that CORS is not necessary for loading There was also another person within the thread that requested a dynamic way of getting the URL, but they had a different reason for wanting this than we do.
@sokra At first this seemed to have promise because it would mean we were hands-off at the point of importing
This might work.
Edit: As an extra complication, the |
I'm pretty sure it will work. |
Actually, me and @sebinsua spoke about this more and I'm not sure how it could work at all. To expand on his comment about |
Let me try it and create a test case to validate if this works correctly. |
Might be useful for us to add a webpack hook somewhere in this file, the ability to extend ContainerPlugins interface is very powerful. By adding another property to the interface, I was able to stream chunks over s3 when running in serverless runtime environments. Going to open a PR with some basic hooks, likely might need some refinements |
Would the hooks be to facilitate extensions via plugins instead of this particular option landing in core? Or is the outcome still dependent on whether or not it is possible to set the public path from the outside? |
Well, I think that this pattern is likely needed as official scope and support grows. That said, if we have hooks in some key locations, you’d technically be able to add additional code like this to remote entry. I’ve had to copy core files over to extend classes for various things -it’s a hassle and would increase flexibility. Of which - one could set their public path or anything else. This PR is not exclusive on implementing hooks in the core. I thought of this PR as I worked on exposing the file system api - both needed to add properties to the interface |
Cool, just trying to get more clarity :) there’s probably some benefit in not exposing functionality and increasing bundle sizes unnecessarily. |
FYI, setting the Here's the branch where I tried this. I'm not sure whether the errror above is expected and unsupported, or whether it means there is a bug in the implementation (@ScriptedAlchemy?). I tested it because I thought that maybe the config you supplied was a method of forcing the module to be in the same entry chunk so perhaps would avoid the chicken-and-egg problem. If this isn't going to be fixed, I'd also be happy if either we landed the code in the OP, or for extension hooks which could add this feature to be created within I tried simpler setups within our codebase, but it turns out that even without a proxy, we can get 404s unless we're able to control the |
Yeah that’ll cause problems. We have in the backlog to write stronger validation and error handling |
@sokra sorry to be pinging you again, but I just wanted to see what the future of this PR is. Would you suggest waiting until extension points are added to the plugin (e.g. via |
@ScriptedAlchemy Hi Zack! Thanks for the excellent work on module federation, truly a game changer. Im working on implementing module federation in a micro frontend project, and need be able to load code in different environments. It seems to me that without the ability to dynamically set the publicPath like this PR will implement, I will have to create a separate bundle for each environment with the publicPath hardcoded for each environment. Im therefore wondering what is happening with this PR, or if there is any other way to avoid having to create separate builds for each environment? |
Can't be switched at runtime.
Neither of these work with |
Is there movement of any sort on this? We're looking into module federation and since we want to deploy the same bundles to our staging environment as to production, this complicates things somewhat. Of course we could build two bundles during CI, but that feels a bit excessive when all we want to do is change a url. |
Another thing btw that is not clear to me - since we use the same bundle to deploy both to staging and production, is it possible to dynamically set the urls for the plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
exports: 'exports@http://localhost:3002/v1/entry.js',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
}),
], |
@chiel yup, just use environment variables aka process.env.NODE_ENV or anything youd like |
Sure, but that's at build time - I meant at run time. |
It's not possible without hacks at the moment. This is also a major problem for us because we deploy the module federation stuff on premise, where we don't know the domains/ips/ports beforehand. /cc @sokra |
@codepunkt What hacks? Has anyone found any workarounds until this feature gets the green light? |
Haven't really seen any movement on this for a while now. Is there a recommended approach to using federated modules with a dynamic public path at runtime? |
Can't set dynamically. but PublicPath:"auto" circumvents the hard coded public paths we have right now. But you can programmatically change it without startup code. |
@ScriptedAlchemy but if you have nested federated modules, each of those would need its publicPath set, no? In our case, the federated modules are being loaded from a cdn - wouldn't setting |
More specifically, how would I achieve this? By creating a function in each remote entry which allows setting the public path, or? |
Hi @mihaisavezi,
After that instead
|
What is the status of this?
|
@sokra I would like to know how to tackle the following situation. ContextImagine a web app similar to Grafana/GCP/AWS/Azure, a Hub of many subproducts. If I would like to decentralize the development of the web app, I will need:
You could see a video related to this: Straw Hat Admin and Wepback Module Federation For example: Given the following routing:
I will take the The subpath allocated to a particular product is dynamic, so you can't assume that And you know where the subsystem bundle deploys until much later. ProblemHow can I bundle the subsystem independently without knowing where they will be deployed? My shell could load from CDN: For subsystem Does that make sense to you? Are you familiar with Grafana? |
@sokra were you ever able to validate this? Per #10703 (comment) I don't think the workarounds outlined in your post would help? |
#10703 (comment) keep publicPath(s) outside of bundle (on window or something else) and set them in runtime |
I am not sure what you mean @alexander-akait would be nice if you share some article, code snippet, or something. Going back to read again the docs, maybe I missed something. |
I'm still not sure how that is applicable to #10703 (comment) because |
This example works, thanks sokra.
Output is
|
for those still reading this. Most cases dynamic public path can be |
What kind of change does this PR introduce?
This change exposes a way to set
publicPath
(see https://webpack.js.org/guides/public-path/#on-the-fly) on a remote so that the remote can still be deployed separately and hosted from/
, but when it's hosted from sayapp/${appName}
(with requests still being routed to the root of the remote host, i.e.remoteHost/
- that's where all the assets live, like JS, CSS etc.), it could be configured to make asset requests to/app/${appName}
.Did you add tests for your changes?
Not yet, opening this draft PR to facilitate a discussion.
Does this PR introduce a breaking change?
No
What needs to be documented once your changes are merged?
How to make use of this feature, what it enables.
Alternative Design
An alternative design would be to expose a mutable property called
__webpack_public_path__
instead ofsetPublicPath
, in order to match the semantics of webpack core more (see https://webpack.js.org/guides/public-path/#on-the-fly). This has the benefit of it being a getter as well, without having to define a matchinggetPublicPath()
.Open Questions
Where to invoke
Once this feature is exposed, what would be the best place to invoke this? In mine and @sebinsua's experiments, we're dynamically loading
remoteEntry.js
in the host app code so we have a way of knowing when it's loaded, and can then checkwindow[remoteIdentifier] !== undefined
and callwindow[remoteIdentifier].setPublicPath(`/app/${appName}`)
.@ScriptedAlchemy has suggested this kind of config:
CommonJS
How would this work in CommonJS environments, would we just ignore it (likely would)?