WP5 Module Federation: remoteEntry.js caching
Asked Answered
S

5

27

With Webpack 5 module federation if remote entry is modified, you don't need to redeploy main module/application and the newest version of the module will be loaded when it's requested by the browser.

I'm wondering: since the remote URL remains the same (e.g. http://localhost:8081/remoteEntry.js), the browser probably will cache the file and cached version loaded every time you load the main module. On the other hand, if you add cache busting for remote entries, you will not have caching.

Let's assume that there is an application with micro-frontend architecture using Webpack 5 Module federation. There is a remote micro frontend with a config like:

output: {
  publicPath: "http://localhost:8081/",
},
plugins: [
  new ModuleFederationPlugin({
    name: "app1",
    filename: "remoteEntry.js",
    exposes: {
      "./Component1": "./src/Component1",
      "./someModule1": "./src/someModule1",
    },
  })
]

and then main module config:

output: {
  publicPath: "http://localhost:8080/",
},
plugins: [
  new ModuleFederationPlugin({
    name: "mainApp",
    filename: "remoteEntry.js",
    remotes: {
      app1: "app1@http://localhost:8081/remoteEntry.js"
    }
  })
]

Both modules are deployed on production.

Then I change Component1 from app1 and deploy app1 module.

How to deal with remote modules caching?

UPDATE:

It looks like in my case the browser uses heuristic caching (https://www.rfc-editor.org/rfc/rfc7234#section-4.2.2) for remoteEntry.js since the server doesn't provide explicit expiration times.

Thus, when remoteEntry.js updates, the main application still loads this file from the cache that could be cached for weeks. For chunks, it's not a problem since webpack could be configured to include hash in the file names.

For remoteEntry.js I see 2 options:

  • cache-busting
  • explicitly specify cache control

Do you think it's a way to go?

Soudan answered 29/12, 2020 at 16:55 Comment(0)
E
20

Cache busting implies to re-build (or, at least, post-process) the main app bundle, and that is one of the problem module federation tries to avoid.

So, considering remoteEntry.js is usually a small file, the best solution is to apply a specific cache control for this file so it never gets cached.

With nginx, it can be done that way:

location ~ .*remoteEntry.js$ {
    expires -1;
    add_header 'Cache-Control' 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
}
Expression answered 22/7, 2021 at 18:4 Comment(2)
worked for me this way, in kubernates nginx.ingress.kubernetes.io/configuration-snippet: | if ($request_uri ~* remoteEntry.js) { expires -1; add_header 'Cache-Control' 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; add_header 'X-Robots-Tag' 'noindex, nofollow'; } add_header 'X-Robots-Tag' 'noindex, nofollow';Sacaton
Take into account that adding headers to a location will overwrite previous defined headers so you should have redefine them within the location statement if you want to preserve such headersShanelleshaner
D
6

You can use ExternalTemplateRemotesPlugin for changing remoteEntry.js url template. I can fix this issue like code below;

new ModuleFederationPlugin({
    name: 'my-app',
    remotes: {
        'my-remote-1': '[email protected]/remoteEntry.js?v=[Date.now()]',
        ...
    },
    ...
}),
new ExternalTemplateRemotesPlugin(),

https://github.com/module-federation/module-federation-examples/issues/566 https://www.npmjs.com/package/external-remotes-plugin

Drawn answered 23/9, 2022 at 12:13 Comment(0)
T
-1

I maybe a bit late to the party but here is one of the solution where you append a random string at the end of your remoteEntry at build time . https://github.com/module-federation/module-federation-examples/issues/566#issue-785273439

Tyrant answered 19/5, 2021 at 13:8 Comment(1)
yes but for that we will have to rebuild the host :(Maxia
P
-1

In my opinion, you should never cache remoteEntry.js since that's where the chunks mapping are. (Mapping here I mean between component and their chunk url). You always want to make sure you are displaying the latest remote chunk. If the mapping changed (usually just means the remote component updated), the browser should fetch the new chunk.

That said, you should cache busting the chunks using

    output: {
        filename: '[name].[contenthash].js',
    },
    plugins: [
        new container.ModuleFederationPlugin({
            name: "RemoteModule",
            library: { type: "var", name: "RemoteModule" },
            filename: "remoteEntry.js",
            exposes: {
                './SuperButton': "./src/components/SuperButton",
                './SuperButton2': "./src/components/SuperButton2",
            },
            shared: {
                react: { singleton: true, eager: true },
                "react-dom": { singleton: true, eager: true },
            }
        }),
        new HtmlWebpackPlugin({
            template: "./public/index.html"
        })
    ],

in your webpack.config.js recommended here https://webpack.js.org/guides/caching/

This way, again, the host will always try to fetch the remoteEntry.js (a fixed url that do not have hash) and let the browser fetch the new chunk url if the hash are changed.

Phenocryst answered 8/8, 2021 at 3:39 Comment(5)
I absolutely agree with you that you shouldn't cache remoteEntry.js and it should always be a 'fresh' version. That said, I don't think the solution proposed by you affects caching of remoteEntry.js. The [name].[contenthash].js is very nice solution for single project since webpack resolves version of [name].[contenthash].js and inserts into index.html in addition, it creates related .js files. With remoteEntry.js it's not the case since the endpoint is specified in separate application and there is no way to get the 'updated' name after micro-frontent application rebuilds.Soudan
IMHO, [name].[contenthash].js can be still used in multiple projects. "With remoteEntry.js it's not the case since the endpoint is specified in separate application and there is no way to get the 'updated' name after micro-frontent application rebuilds." With this comment -- the other host does not have to know the chunk urls right? In order to use the an exposed remote component, you only need to know the remoteEntry.js url and the exposed public name.Phenocryst
I might have misunderstood you. But for clarification, the remoteEntry.js url should never change between builds (therefore cannot have hash in the url). The only time to change it might be because the remote component have a breaking change or some other reason that needed the remoteEntry.js to have a version bump. Rest of the remote chunks urls should be hashed. Let me update the code snippet I have added above to include the filename attribute in the ModuleFederationPlugin to make it clear. If we add the filename attribute, the remoteEntry.js name will not have the hash.Phenocryst
And it will be a fixed url. Let me know if that make sense! @PavloKozlovPhenocryst
I think we're talking about the same things ;) Indeed, you could use contentHash in chunk names and remoteEntry.js (the endpoint itself) stays the same with cache policy -> no-cache. See answer from @Expression aboveSoudan
D
-3

I don't think this is a concern since webpack adds cache busting to your application's js files during the build process. Take a look at the HTML code that is returned when you load your app and you will notice that the tags that load your bundle are different after each deployment. remoteEntry.js is the module federation way of allowing your app to be loaded from a remote server, not your actual app code.

Depilate answered 29/12, 2020 at 21:40 Comment(4)
You're right in terms of the fact that webpack adds hashes for file names when builds files. With remoteEntry.js it's a little bit different. There is no cache buster or hash in the file name. It's just remoteEntry.js. I tested it on a simple application and remoteEntry.js resolves sometimes with 200 sometimes with 304 http status code. I'm not sure what is the reason behind it.Soudan
how's is that affecting your application? are you seeing a cached version? if so it could be something else. Probably an issue with your hosting provider caching your HTML page, not remoteEntry.jsDepilate
For me, it works ok. I created another example and deployed it on heroku. remoteEntry.js resolves with 200 code when I load it the first time or when the remote version has been changed. Otherwise, it resolves with 304 http status code. I just want to be sure how caching works here before migrating the existing application and deploy it on production environment.Soudan
I also updated the post with the latest findings and possible solutions.Soudan

© 2022 - 2024 — McMap. All rights reserved.