I have set up multiple microfrontends using an "app-shell" type of application for the domain root, with each microfrontend on the first path element. Each app is constructed as a standalone angular application utilizing shared libraries to reuse common components and functionalities. They are contained in their own separate docker container and connected through nginx proxy maps.
mydomain.com/ <- domain root app
/child1 <- micro apps
/child2
/child3
/child4
I am looking to implement an angular service worker to cache all our resources.
Initial approach
The initial approach was to have one service worker per app. This means that the domain-root app had one, as well as each of our "child-apps." There was one ngsw-config.json file per standalone angular app.
Issue
This approach led to conflicts between two service workers controlling the caching in each child-app, resulting in users potentially seeing an older version depending on which service worker won.
Second attempt
The second attempt involved having one service worker to manage all apps. It was placed on the domain-root app, with the aim of caching every "child-app's" resources by using ngsw-config.json's assetGroup.resources.files or assetGroup.resources.urls. However, this did not seem to effectively map the resources of the child-apps into the cache storage. Although the network tab indicated that the resource files were being served from the ServiceWorker. 🤔
Problem
The issue here is that the apps no longer notify when a new version is deployed. This may be due to the angular-cli not recognizing the names of compiled resources during build time. These resources have to be cached at runtime. Additionally, there is uncertainty about whether any resources are being cached at all, as offline mode does not yield any response upon toggling.
Furthermore, there are instances where an older version of the domain-root app is loaded initially, requiring a page refresh to access the updated version. Code has been inserted to remove the second service worker from the initial 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 involves Cache storage in DevTools/Application, showing exactly 4 entries per new version hash, retaining all versions instead of overwriting the cache with each new version.
Current setup:
Using @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) {
// Ensure running on an updated version
if (updates.isEnabled) {
// Prompt user for permission to update upon registration of a new update
updates.available.subscribe(() => {
if (confirm('A newer version of the application is available. Load the new version?')) {
updates.activateUpdate().then(() => document.location.reload());
}
});
// Check for updates every 5 minutes
timer(0, 5 * 60 * 1000).subscribe(n => updates.checkForUpdate());
} else {
console.log('SW not enabled!');
}
}
Question
How should the configuration be adjusted to ensure proper caching of resources by the service worker(s?) and prevent them from conflicting with each other (if individual service workers per app are necessary)?
EDIT
For those encountering similar challenges; this issue could potentially be resolved by leveraging the new Module Federation system introduced in Webpack 5, expected to be released with Angular 11 (https://www.angulararchitects.io/aktuelles/the-microfrontend-revolution-part-2-module-federation-with-angular/).