3

My company is working with a micro-frontend architecture for our application. It's a project I've been pioneering here and it has been quite successful so far. However, I'm hoping to get some advice on one of our outstanding challenges.

When you build a JavaScript application using Webpack, one of the options is to add a hash to the URL. This hash is generated with each build, so the hash would only change if there are changes to the file itself. So the filename would look like this:

app.ab12cd.js

The advantage to doing this is browser caching. Browsers will try to cache things to avoid having to consume too much data. So if it sees that same filename/URL again, it will just used the cached version rather than re-downloading it. Because the hash in the filename will only change if the file is re-built with changes, we can safely rely on the browser caching this file to reduce the over-the-wire download burden on our users while still being confident that they will always see the latest changes.

This is a challenge with our micro-frontend architecture. One of the guiding principles is for each micro-frontend to be individually releasable, meaning there is no direct dependency between the base application (ie, the initial one that the user navigates to) and the micro-frontend app that it will load.

We accomplish this through simple, static tags. Each time we add a new micro-frontend, we only need to update the base application once to add a new tag:

<script src="micro-frontend/assets/js/app.js"></script>

In the example above, that URL is redirected using an Nginx proxy to the actual, deployed micro-frontend. It's a relative URL for stupid and frustrating reasons involving our corporate infrastructure, but that's a whole other tangent.

The main point is you'll notice that it's pointing to "app.js", and not "app.12ab34.js". We're not using hashes because we don't want to update the base application each time the micro-frontend changes. Instead, we are returning a Cache-Control header to prevent any browser caching of the micro-frontend.

This also is not ideal, because while we gain our independence, we lose the caching of our micro-frontend code.

So, my question: if we were to enable hashes in the filenames on the micro-frontends, is there a way that the base application can be set up where we won't have to update it for hash changes? To put it differently, is there a completely different way of connecting these apps that I haven't thought of yet?

halfer
  • 19,824
  • 17
  • 99
  • 186
craigmiller160
  • 5,751
  • 9
  • 41
  • 75
  • there's a lot of unnecessary information here. it's probably not necessary to explain browser caching to the people you expect to get help with your browser caching issue from, for example. – I wrestled a bear once. Aug 28 '19 at 13:15
  • a [service worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) might be the answer you're looking for. you can manage your caching very precisely with a service worker. – I wrestled a bear once. Aug 28 '19 at 13:21
  • Using temporary redirects from app.js to the actual hashed files should work. The redirect is not cached, but the actual file can be cached. Another approach would be to use an ETag. – str Aug 28 '19 at 19:54

2 Answers2

0

You are mixing concerns - cache busting and getting a specific version of a package.

You might not want to update your main apps <script> for only small updates, but in the end you need somehow to manage what version of your individual assets should be used. After all this is why you are probably doing micro-frontends after all.

There are many options to accomplish this; one option that gives you the flexibility of not changing the main app, whenever a new version of a package gets released, is using a reverse-proxy, that is doing something like unpkg.com hooked up to your artifact repository (e.g. an npm registry):

  • -> unpkg.com/:package@:version/:file if you employ this pattern, you can not only update your version, but get the benefits of semantic versioning as well. You can release both minor updates along with major ones without affecting your main apps

  • Thus <script src="micro-frontend/assets/js/app.js"></script>would become something like <script src="my-own-nginx/@micro-frontend/assets-a@1.2.*/"></script>

Of course you still need to come up with proper client caching (i.e. sending the right headers).

Another option is, instead of doing a static composition of your components in your main app, you can do it on runtime:

  • fetch the most recent list of components from a service (for the sake of simplicity a package.json would also suffice)
  • dynamically create the <script> tags to load the components

This would allow to use direct hashes and you would not have to bother with client-side cache busting.

Both approaches can of course be mixed as well - generate a build time dep file for your main app, let's call it package.json and dynamically load this and dynamically generate <script> tags that will load you components off your unpkg-compatible reverse-proxy.

The software for unpkg.com is open source and can easily be adjusted to be associated with your own private npm registry.

For a simple PoC you could directly use unpkg.com directly.

Christian Ulbrich
  • 3,212
  • 1
  • 23
  • 17
-1

If you are building your microfrontend using Webpack, use the WebpackManifestPlugin to generate a manifest.json in the output dir. It maps your hash output name to a constant "main" property in the json object. Then when you want to load the microfrontend from your main app just do a fetch (no-cache headers and stuff) to read the manifest. Then use scriptjs to dynamically load the actual bundle by the name you got from the manifest.

Alex Vukov
  • 102
  • 6