SAPUI5, SAP

SAP MII SAP UI5 Application Cache buster

This blog post explains the challenges of cache control while managing changes in SAP UI5 application which are not hosted through Fiori front end server and how we can deal with that.Like in SAP manufacturing suit, SAP Manufacturing Intelligence and Integration is and Netweaver Java AS application where the SAP UI5 applications will be running on the same server independently boot strapped with the local library / UI component installed on the AS. Though we have AppCacheBuster and cache buster boot strap in UI5 application but those will not help in this kind of scenario.

Also Read: SAP Certifications

We will see how we can create our own custom module for cache busting and use in our application development.

Browser Cache:

The browser cache is a temporary storage location on your computer for files downloaded by your browser to display websites. Files that are cached locally include any documents that make up a website, such as html files, CSS style sheets, JavaScript scripts, as well as graphic images and other multimedia content.

When you revisit a website, the browser checks which content was updated in the meantime and only downloads updated files or what is not already stored in the cache. This reduces bandwidth usage on both the user and server side and allows the page to load faster. Hence, the cache is especially useful when you have a slow or limited Internet connection.

The issue of cache on UI5 application change management :

while cache has been introduced in browsers for overall performance improvement of web applications, this also has an significant pain in application change management. When the application development team deploy any new changes either in UI component or the controller functionality , to make this changes effectively available to the end users the browsers cache must be cleared. Otherwise upon visiting the application url, browser always tries to load the resources from the disk cache or memory cache first. Which will not load any new changes, that has been deployed on the server.

With this limitation in place the concepts like AppCacheBuster or cache buster has been introduced. But in the kind of application that are of focus here this concepts are not of much help. The gap still remains here.

We will see how we can write our custom CacheBuster module and use it in UI5 application.

If we try to look into the UI5 library and how it loads the modules like views controller in the browser we can see there is an initiator ui5loader-dbj.js which takes care of all the module processing in UI5 for synchronous as well as asynchronous processing. It basically uses below ways to get the modules from the server.

  • creating and XMLHttpRequest().open with the module url.
  • Injecting child HTML <script> tags with source as module url.

Writing CacheBuster Module:

Before we start , we are going to have application versioning maintained through a file on server and we will override XMLHttpRequest().open method and Element.appendChild() method in our custom CacheBuster.js Module created in the server and incorporate the module in index.htm.

below is how we are going to do this.

  • Create a version.json file and place it in the same directory root as our index.html . It will be an simple json file with below structure.
{
	"_AppVersion" : "1.1"
}
  • Create CacheBuster.js in application web root. path used in the examples are “/webapp/controller/CacheBuster.js” and “/sripts/CacheBuster.js”.
  • Defining the module
sap.ui.define(["sap/ui/core/ComponentSupport"], function () {
	"use strict";
});
  • Access the version.json with jquery get method appending timestamp in the url to ensure we get version file always from server.
$.get("./version.json?" + "&__=" + new Date().getTime(), function (data) {
		//.....//
               return;
	}, 'json');
  • Once application version is available , Let’s override the XMLHttpRequest().open method and Element.appendChild() function and in all module urls append “&_AppVersion=<the version in json file>”.
(function () {
			var proxied = window.XMLHttpRequest.prototype.open;
			window.XMLHttpRequest.prototype.open = function () {
				var url = arguments[1];
				if (url.indexOf("?") == -1) {
					url = url + "?";
				}
				arguments[1] = url + "&_AppVersion=" + data._AppVersion;
				return proxied.apply(this, [].slice.call(arguments));
			};
			var proxiedDomappend = Element.prototype.appendChild;
			Element.prototype.appendChild = function () {
				console.log(arguments);
				if (arguments[0].tagName === "SCRIPT" || arguments[0].tagName === "LINK") {
					var url = arguments[0].src !== undefined ? arguments[0].src : arguments[0].href;
					if (url.indexOf("?") == -1) {
						url = url + "?";
					}
					if (arguments[0].src !== undefined) {
						arguments[0].src = url + "&_AppVersion=" + data._AppVersion;
					} else if (arguments[0].href !== undefined) {
						arguments[0].href = url + "&_AppVersion=" + data._AppVersion;
					}

					return proxiedDomappend.apply(this, [].slice.call(arguments));
				} else {
					return proxiedDomappend.apply(this, arguments);
				}
			};
		})();

Finally the Module looks like this.

CacheBuster.js :

sap.ui.define(["sap/ui/core/ComponentSupport"], function () {
	"use strict";

	$.get("./version.json?" + "&__=" + new Date().getTime(), function (data) {
		(function () {
			var proxied = window.XMLHttpRequest.prototype.open;
			window.XMLHttpRequest.prototype.open = function () {
				var url = arguments[1];
				if (url.indexOf("?") == -1) {
					url = url + "?";
				}
				arguments[1] = url + "&_AppVersion=" + data._AppVersion;
				return proxied.apply(this, [].slice.call(arguments));
			};
			var proxiedDomappend = Element.prototype.appendChild;
			Element.prototype.appendChild = function () {
				console.log(arguments);
				if (arguments[0].tagName === "SCRIPT" || arguments[0].tagName === "LINK") {
					var url = arguments[0].src !== undefined ? arguments[0].src : arguments[0].href;
					if (url.indexOf("?") == -1) {
						url = url + "?";
					}
					if (arguments[0].src !== undefined) {
						arguments[0].src = url + "&_AppVersion=" + data._AppVersion;
					} else if (arguments[0].href !== undefined) {
						arguments[0].href = url + "&_AppVersion=" + data._AppVersion;
					}

					return proxiedDomappend.apply(this, [].slice.call(arguments));
				} else {
					return proxiedDomappend.apply(this, arguments);
				}
			};
		})();

		return;
	}, 'json');

});

Now Let us look at the application file system for both AMD and non AMD way of writing and how to use the CacheBuster.js Module in the application.

AMD Approach:

AMD( Asynchronous Module definition ) is a way of writing your UI5 application in an overall asynchronous way which has significant benefit on app performance .

Folder Structure:

index.html :

We will use below ui5 config parameter to anchor our module on init of the bootstrapping

data-sap-ui-oninit=”module:VersionAMD/VersionAMD/controller/CacheBuster”
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
		<title>VersionAMD</title>
		<script id="sap-ui-bootstrap"
			src="resources/sap-ui-core.js"
			data-sap-ui-theme="sap_fiori_3"
			data-sap-ui-resourceroots='{"VersionAMD.VersionAMD": "./"}'
			data-sap-ui-compatVersion="edge"
			data-sap-ui-oninit="module:VersionAMD/VersionAMD/controller/CacheBuster"
			data-sap-ui-async="true"
			data-sap-ui-frameOptions="trusted">
		</script>
	</head>
	
	<body class="sapUiBody">
		<div data-sap-ui-component data-name="VersionAMD.VersionAMD" data-id="container" data-settings='{"id" : "VersionAMD1"}'></div>
	</body>
</html>

NonAMD Approach :

Folder Structure :

index.html :

we will declare our module using the below line.

jQuery.sap.require(“VersionNonAMD.CacheBuster”);
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
	    <meta name="viewport" content="width=device-width, initial-scale=1.0">
		<title>VersionNonAMD</title>
		<script id="sap-ui-bootstrap"
			src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
			data-sap-ui-theme="sap_fiori_3"
			data-sap-ui-compatVersion="edge"
			data-sap-ui-libs="sap.m, sap.tnt"
			data-sap-ui-async="true">
		</script>
	</head>
	<script >  	       
		jQuery.sap.registerModulePath("VersionNonAMD","./scripts/");
		jQuery.sap.require("VersionNonAMD.CacheBuster");
		jQuery.sap.require("sap.tnt.ToolHeader");
		jQuery.sap.require("sap.m.MessageBox");
		sap.ui.getCore().attachInit(function () {
			var oView = sap.ui.view({
							id : "VersionNonAMD",
							viewName : "VersionNonAMD.VersionNonAMD",
							type : sap.ui.core.mvc.ViewType.XML
						});
				
						oView.placeAt("content");
		 });
	</script>
	<body class="sapUiBody">
		<div id = "content"></div>
	</body>
</html>

Finally when we run the application :

How the network requests look for version 1.1 :

Let’s update the app version in version.json file as 1.2

How the network requests look for version 1.2 :

!!.. Yes it’s coming from server again for the new version deployed. We have achieved the cache buster using our custom module and application versioning

Leave a Reply

Your email address will not be published. Required fields are marked *