Skip to Content
Technical Articles
Author's profile photo Shashank Kumar

Enable SAPUI5 application caching using Google Workbox

Hello Everyone.

In this blog post we will learn the steps to cache files (URL response) in a SAPUI5 application with help of Google Workbox and will be a step closer to being a Progressive Web App.

More can be found about Google Workbox here https://developers.google.com/web/tools/workbox.

The various features that can be added to a progressive web application can be found here https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps

The scope of this post will be limited to use of service worker api in the browser. The files loaded by application is cached in browser during first load. The subsequent requests are intercepted and the files are returned from cache. You can learn more about Service Workers here https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker.

We will learn

SAPUI5 application structure

You can refer https://developers.sap.com/tutorials/sapui5-webide-create-project.html to create a SAPUI5 application. Here, I created a SAPUI5 application with name ‘sw-cache’. The structure of a SAPUI5 application looks like below.

(Fig. 1 – Project structure)

The files of our interest is index.html and we will add a new file sw.js at same level with index.html. Service Worker registration happens in index.html and the initialization and configurations are added in sw.js.

Registering a Service Worker

Initialize Service Worker in index.html by adding the script below.

	<script>
		if ('serviceWorker' in navigator) {
			navigator.serviceWorker.register('sw.js')
			  .then(function(registration) {
				// Registration was successful
				console.log('[success] scope: ', registration.scope);
			  }, function(err) {
				// registration failed :(
				console.log('[fail]: ', err);
			  });
		}
	</script>

In sw.js file, we define the route patterns to be cached, the details are commented alongside code. We can use multiple caches based on file types or routes, or just one cache. The regex route pattern input to map a cache can be tailored based on the design.

importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.0.0/workbox-sw.js');

// when we use CacheFirst strategy, the file is loaded from network if it is not there in cache and is // then fetched from cache for every subsequent GET requests.
// NetworkFirst strategy will always get it from the network.

// cache javascript type files
workbox.routing.registerRoute(	
	new RegExp('.*\.js'),
	new workbox.strategies.CacheFirst({	
		cacheName: "jsCache",	
		plugins: [	
			new workbox.expiration.Plugin({	
				maxEntries: 30,	
				// Cache for a maximum of 3 days	
				maxAgeSeconds: 3 * 24 * 60 * 60	
			})	
		]	
	})	
);

workbox.routing.registerRoute(	
	new RegExp('.*\.properties'),	
	new workbox.strategies.CacheFirst({	
		cacheName: "msgPropCache",	
		plugins: [	
			new workbox.expiration.Plugin({	
				// Cache only 60 images	
				maxEntries: 60,	
				// Cache for a maximum of 3 days	
				maxAgeSeconds: 3 * 24 * 60 * 60	
			})	
		]	
	})	
);

 

Running the application

Once we run the above application, and open the debugger console (Google Chrome in this case), we can see service worker cache created for this application and the files it has cached. This is explained using screenshots below. (Zoom In or Open in New Tab to see larger image)

(Fig. 2 – jsCache in the Cache Storage under Application tab)

(Fig. 3 – msgPropCache in the Cache Storage under Application tab)

(Fig. 4 – Application files in network, before using Google Workbox for service worker cache)

When we load the SAPUI5 application ‘sw-cache’ again and see the network calls, we see that the file types added in cache configuration are loaded from cache (the routes are intercepted by Google Workbox to load files from cache).

(Fig. 5 – Application files in network, after using Workbox for service worker cache)

We can notice the difference in routes of the file loaded and total application load time.

*For some errors that we see for messagebundle.properties, a different cache configuration was needed as it maps to cross origin route of sapui5.hana.ondemand.com. We can see the configurations for the same in later posts and messagebundle.properties cache can be ignored for now.

Deleting and Updating the cache

Let us assume that we have released a new version of our application and now the cache needs to be updated in users’ system.

  • The cache expiration can be configured using ‘maxAgeSeconds’ as shown in code below or we can delete the old cache and create a new one. The cache expiration with using ‘maxAgeSeconds’  gives us less control.
  • When we delete old cache and create new one, the new/updated files will be loaded again and will point to a new cache.

Here, we added an ‘activate’ listener to the service worker. When we do application deployment, we also add changes to our service worker file (by adding/updating ‘cacheName’ using a ‘cacheId’ variable). The browser knows by byte check that there is a new service worker for the application, and then it will install and activate the new one. Just before activation takes place, we clear the old cache and map all configurations to new cache. Now, all files will be cached in the new cache and will be loaded from there.

importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.0.0/workbox-sw.js');

// when we use CacheFirst strategy, the file is loaded from network if it is not there in cache and is // then fetched from cache for every subsequent GET requests.
// NetworkFirst strategy will always get it from the network.

var cacheId = "development-cache";
// var cacheId = "1.0.1"; //app release version
var jsCache="jsCache-" + cacheId;
var msgPropCache="msgPropCache-" + cacheId;
var cacheList = [jsCache, msgPropCache];

// cache javascript type files
workbox.routing.registerRoute(	
	new RegExp('.*\.js'),
	new workbox.strategies.CacheFirst({	
		cacheName: jsCache,	
		plugins: [	
			new workbox.expiration.Plugin({	
				maxEntries: 30,	
				// Cache for a maximum of 3 days	
				maxAgeSeconds: 3 * 24 * 60 * 60	
			})	
		]	
	})	
);


workbox.routing.registerRoute(	
	new RegExp('.*\.properties'),	
	new workbox.strategies.CacheFirst({	
		cacheName: msgPropCache,	
		plugins: [	
			new workbox.expiration.Plugin({	
				// Cache only 60 images	
				maxEntries: 60,	
				// Cache for a maximum of 3 days	
				maxAgeSeconds: 3 * 24 * 60 * 60	
			})	
		]	
	})	
);


self.addEventListener('install', function(event) {
  self.skipWaiting();
});

// Call Activate Event to remove old cache
self.addEventListener('activate', function(e) {
  // Remove unwanted caches
  e.waitUntil(
    caches.keys().then(function(cacheNames){
      return Promise.all(
        cacheNames.map(function(cache) {
        	// If cache Id is set to 'development-cache', it is deleted with every reload, when 'update on reload' is checked in Service Workers, under the 'Application' tab in debugger console
        	if(cacheId.indexOf("development-cache") > -1 || cacheList.indexOf(cache) === -1) {
        		console.log("cache deleted");
        		return caches.delete(cache);
        	}
        })
      );
    })
  );
});

*In a development environment like SAP Web IDE, the service worker gets activated during development and cause problems with cache. After the application is loaded, open Google Chrome Browser debugger and check ‘Update on reload’ in the Service Workers section in the ‘Application’ Tab. This is to re-install service worker every time for the application loaded via SAP Web IDE route. Every time service worker is activated, we can delete the old ‘development-cache’ and create new one again, as shown above. This problem can also be solved, if service worker is disabled in development environment.

Automating the ‘cacheName’ generation and changing it in sw.js file. We will be adding a Grunt task for the same in Gruntfile.js. This will update the ‘cacheId’ in sw.js with every deployment build and hence will help in clearing old cache in the users’ browser.

module.exports = function(grunt) {
	"use strict";
	grunt.loadNpmTasks("@sap/grunt-sapui5-bestpractice-build");
	grunt.loadNpmTasks("@sap/grunt-sapui5-bestpractice-test");
	grunt.registerTask('cache-version', function(key, value) {
		// reads the service worker file from dist folder and updates the version of cache and writes it back.
        var serviceWorkerFilePath = "dist/sw.js";
        if (!grunt.file.exists(serviceWorkerFilePath)) {
            grunt.log.error("file " + serviceWorkerFilePath + " not found");
            return true;
        }
        var file = grunt.file.read(serviceWorkerFilePath);
        var result = file.replace("development-cache", Date.now() + "");
        grunt.file.write("dist/sw.js", result);
    });
	
	grunt.registerTask("default", [
		"clean",
		"lint",
		"build",
		"cache-version"
	]);
	
};

 

The package.json configuration is

{
  "name": "sw-cache",
  "version": "0.0.1",
  "description": "",
  "private": true,
  "devDependencies": {
    "@sap/grunt-sapui5-bestpractice-build": "1.3.64",
    "@sap/grunt-sapui5-bestpractice-test": "^2.0.1",
    "eslint": "^5.16.0",
    "grunt": "^1.0.4",
    "vscode-uri": "1.0.6"
  }
}

 

This sums up the idea of adding cache in SAPUI5 applications with help of Google Workbox and manage its life cycle.

Assigned Tags

      Be the first to leave a comment
      You must be Logged on to comment or reply to a post.