Angular PWA in microfrontends

2.4k views Asked by At

I have several microfrontends setup using an "app-shell" sort of app for the domain root and each microfrontend on the first path element. Each app is built like a standalone angular application using shared libraries to reuse common components and functionality. They live in their own separate docker container and are bound together using nginx proxy maps.

mydomain.com/         <- domain root app
            /child1   <- micro apps 
            /child2
            /child3
            /child4

I want to setup an angular service worker to cache all our resources.

First attempt

Have one service worker per app. So the domain-root app had one, and each of our "child-apps" had one. That is; one ngsw-config.json per standalone angular app.

Problem

This resulted in two SW's fighting over cache control in each child-app, and users could end up seing an older version depending on which won.

Second attempt

Have one service worker to rule them all, place it on the domain-root app and have it try to cache every "child-app"s resources using ngsw-config.json's either assetGroup.resources.files or assetGroup.resources.urls. Neither of those would seem to map the child-apps resources into its cache - at least not when inspecting DevTools AppCache repository, allthough the network tab does report that the resource file is being served from the ServiceWorker.

Problem

The problem here is that the apps no longer report when a new version is deployed. I guess this is because the angular-cli have no way of knowing the name of the compiled resources of child-apps build-time. They have to be cached run-time. Anyways, I'm not absolutely certain that any of my resources are cached at all. By toggling to offline mode, nothing responds.

Also we still sometimes experience an older version of the domain-root app being loaded firstly, and we have to refresh the page to get the updated version. I have injected code to cleanup the second service-worker from my first attempt. The following code removes all service workers registered to mydomain.com/childX:

// Force remove old redundant service workers
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.getRegistrations().then(registrations => {
    registrations
      .filter(r => 'scope' in r && r.scope !== `${location.protocol}//${location.host}/`)
      .forEach(r => r.unregister());
  });
}

Another observation I see is that in DevTools / Application / Cache storage, I have exactly 4 entries per new version hash, and it keeps all versions. I thought that it would overwrite the cache when a new version was found.

Current setup:

Running @angular/service-worker: 10.1.4

ngsw-config.json

    {
      "$schema": "./node_modules/@angular/service-worker/config/schema.json",
      "index": "/index.html",
      "assetGroups": [
        {
          "name": "root",
          "installMode": "prefetch",
          "updateMode": "prefetch",
          "resources": {
            "files": [
              "/assets/icons/favicon.ico",
              "/index.html",
              "/manifest.webmanifest",
              "/*.css",
              "/*.js",
              "/*.txt"
            ]
          }
        },
        {
          "name": "apps",
          "installMode": "lazy",
          "updateMode": "prefetch",
          "resources": {
            "files": [
              "/**/assets/icons/favicon.ico",
              "/**/index.html",
              "/**/manifest.webmanifest",
              "/**/*.css",
              "/**/*.js",
              "/**/*.txt"
            ]
          }
        },
        {
          "name": "root-assets",
          "installMode": "prefetch",
          "updateMode": "prefetch",
          "resources": {
            "files": [
              "/assets/**",
              "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
            ]
          }
        },
        {
          "name": "apps-assets",
          "installMode": "lazy",
          "updateMode": "prefetch",
          "resources": {
            "files": [
              "/**/assets/**",
              "/**/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
            ]
          }
        }
      ]
    }

app.component.ts

  constructor(updates: SwUpdate) {
    // Make sure we run on an updated version
    if (updates.isEnabled) {
      // Ask user for permission to update once a new update is registered
      updates.available.subscribe(() => {
        if (confirm('A newer version of the application is available. Load the new version?')) {
          updates.activateUpdate().then(() => document.location.reload());
        }
      });
      // Check every 5 minutes for updates
      timer(0, 5 * 60 * 1000).subscribe(n => updates.checkForUpdate());
    } else {
      console.log('SW not enabled!');
    }
  }

Question

How should this be configured in order for the service worker(s?) to be able to cache my resources properly, and (if one per app is required) not work against each other?

EDIT

For others struggling with this issue; Possibly this can be solved using the new Module Federation system from Webpack 5, said to be released with Angular 11 (https://www.angulararchitects.io/aktuelles/the-microfrontend-revolution-part-2-module-federation-with-angular/).

1

There are 1 answers

0
Danilo Santos On

I had de same problem, but I resolved it serving a json file to main app with de sub apps versions. And cached the sub apps by your hashed bundle.