23

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?

Community
  • 1
  • 1
Pavlo Kozlov
  • 966
  • 1
  • 9
  • 22

5 Answers5

19

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';
}
ebrehault
  • 2,471
  • 21
  • 22
  • 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'; – Kames Guen Jun 10 '22 at 09:08
  • 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 headers – Gerard_dev Apr 25 '23 at 09:07
4

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': 'my-remote-1@my-domain.com/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

mg-fix
  • 41
  • 2
0

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.

RobotCharlie
  • 1,180
  • 15
  • 19
  • 1
    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. – Pavlo Kozlov Aug 10 '21 at 07:50
  • 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. – RobotCharlie Aug 10 '21 at 20:26
  • 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. – RobotCharlie Aug 10 '21 at 20:43
  • And it will be a fixed url. Let me know if that make sense! @PavloKozlov – RobotCharlie Aug 10 '21 at 20:50
  • 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 @ebrehault above – Pavlo Kozlov Aug 11 '21 at 12:46
-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

Xiaofeng Xie
  • 145
  • 9
-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.

Ruben Casas
  • 113
  • 3
  • 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. – Pavlo Kozlov Dec 30 '20 at 15:40
  • 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.js` – Ruben Casas Dec 30 '20 at 17:41
  • 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. – Pavlo Kozlov Dec 31 '20 at 10:59
  • I also updated the post with the latest findings and possible solutions. – Pavlo Kozlov Dec 31 '20 at 11:59