Overview and motivation

I decided a while ago to write this blog post after a call I had with a customer who struggled with the startup performance of his SAPUI5 application. The initial steps taken to improve the situation actually worsened it and the reasons for this behavior were not understood.

The goal of this post is to outline some of the basic principles which need to be understood in terms how SAPUI5 loads its own resources and those of applications. Optimizing the choreography is key to getting the best possible startup performance. In addition, we will look at which effect a high latency can have on your performance and how to best tackle this issue as well using the AKAMAI network.

We will use a very simple application for this exercise, one which nonetheless has all of the key structures and artifacts found in “typical” applications and thus serves quite well to illustrate the mechanics.

You can get the sources of the sample application here:
https://github.com/frdrcbrg/ui5-startup-performance

The initial startup time of this application is around 8s – we will optimize it to around 2s.

Understand the application structure

The application consists of all typical folders and their included artifacts.

  • controller: Javascript files with the controller implementations for the corresponding views
  • css: Application specific CSS files (if required)
  • i18n: Contains the properties files which contain the application’s resource files in their respective translation
  • model: Application specific models and their respective modules for implementation
  • util: Typically a set of Javascript files needed by the application (folder name is not standardized, but most applications have such a folder with additional JS code)
  • view: Typically XML files for all view definitions
  • Component.js: Modern applications (which follow the official SAPUI5 best practices and all modern SAPUI5 Fiori applications) have this file which represents the application component
  • manifest.json: This file is the metadata description of the application and also found in most modern SAPUI5 applications


Understanding the bootstrapping within index.html

<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta charset="UTF-8">

    <title>Performance Best Practices</title>

    <script id="sap-ui-bootstrap"
        src="../../resources/sap-ui-core.js"
        data-sap-ui-libs="sap.m, sap.ui.table, sap.ui.commons, sap.ui.ux3"
        data-sap-ui-theme="sap_bluecrystal"
        data-sap-ui-compatVersion="edge"
        data-sap-ui-preload="sync"
        data-sap-ui-resourceroots='{"LatencyDemo": ""}'>
    </script>

    <script>

	    jQuery.sap.require('LatencyDemo.util.File1');
	    jQuery.sap.require('sap.ui.core.mvc.XMLView');
	
	    new sap.ui.xmlview({viewName:"LatencyDemo.view.View1"}).placeAt("content");

    </script>
</head>

<body class="sapUiBody" id="content">
</body>

This is a pretty standard index.html content as you are sure to find it in many applications. There are certain things to notice however:

  1. Large list of libraries: 4 libraries are configured for immediate loading
  2. Synchronous Preload: data-sap-ui-preload=”sync” is the default setting and most likely active in many applications (since the setting is most often omitted)
  3. Script: Direct usage of Javascript APIs in the inline script tag – this means a direct and synchronous processing after all previous scripts are processed by the browser

Now let’s have a look at the network trace of this application when it’s started (from a server with a somewhat high latency).


Notice the waterfall like loading of synchronous sequential requests of the core and all required libraries. 28 requests with a total time of about 8 seconds.

The contents of the libraries (usually a high number of individual Javascript files) are bundled into single files which are called library-preload.js (or JSON, depending on your UI5 version). These are the files which consume most of the initial startup time.

This is one of the performance anti-patterns which we need to avoid. Instead, we should try to load as many files in parallel as possible, which fortunately is supported by SAPUI5 when it comes to loading your required libraries.

Step 1: Loading SAPUI5 libraries asynchronously

Going back to the original reason for this post, the customer had also tried this. Let’s do it just like they tried to fix the issue – by letting SAPUI5 load its resources asynchronously.

Here’s how the application was changed:

    <script id="sap-ui-bootstrap"
        src="../../resources/sap-ui-core.js"
        data-sap-ui-libs="sap.m, sap.ui.table, sap.ui.commons, sap.ui.ux3"
        data-sap-ui-theme="sap_bluecrystal"
        data-sap-ui-compatVersion="edge"
        data-sap-ui-preload="async"
        data-sap-ui-resourceroots='{"LatencyDemo": ""}'>
    </script>

Notice the change to the setting data-sap-ui-preload – now to “async”. 

With this additional setting in place, let’s check out the network trace again:

Oops. 94 requests with a total loading time of almost 19 seconds. 

(In the customer’s scenario, the total loading time went up to about 150 seconds with a total of around 400 request.)

So what went wrong? The answer is in the script tag on the index.html page.

    <script>

	    jQuery.sap.require('LatencyDemo.util.File1');
	    jQuery.sap.require('sap.ui.core.mvc.XMLView');
	
	    new sap.ui.xmlview({viewName:"LatencyDemo.view.View1"}).placeAt("content");

    </script>

Some background on how browsers work: A browser will process script tags in the HEAD tag of the page sequentially. This means that the first script tag which loads the SAPUI5 core.js file is loaded and executed first and the code above is executed directly afterwards.

Before we switched to the asynchronous loading, the first script tag was in charge or loading all required libraries and did this in a synchronous way. This means that the browser was blocked by this action and did not yet get to the inline script tag shown above.

This also means that all libraries were loaded and available to the application code when the script was reached, so that the jQuery.sap.require calls and other usages of the API could rely on already available functionality and modules.

Since we switched to asynchronous loading, the first script tag which loads the SAPUI5 Core is loaded and schedules the loading of all other libraries but does not wait for them to be fully available. Instead, its processing is eventually completed and the browser can continue with the next script tag – our inline script.

Since this code is now executed WHILE the browser is still waiting for the libraries to be fully loaded, any synchronous API which loads content from these libraries must attempt to fetch the required modules via a separate synchronous AJAX call.

And since all SAPUI5 modules typically require more modules, which in turn require even more modules and so on, a huge cascade of synchronous sequential requests is issued until the entire dependency chain is resolved and fully loaded. Remember, all of this is happening while the browser is trying to load the libraries in parallel.

So how can we avoid this situation? By using the right sync point.

Step 2: Using the event attachInit of the SAPUI5 core

 

SAPUI5 has an event to notify an application that all initially required libraries are fully loaded. Here’s a code showing how to use it:

    <script>

		sap.ui.getCore().attachInit(function(){
    		jQuery.sap.require('LatencyDemo.util.File1');
    		jQuery.sap.require('sap.ui.core.mvc.XMLView');
    
    		new sap.ui.xmlview({viewName:"LatencyDemo.view.View1"}).placeAt("content");		  
		});

    </script>

By wrapping our application code in the callback function passed to the attachInit event, we can ensure that all SAPUI5 libraries are available and thus can avoid the additional sync requests.

This is the new network trace:

Much better. 29 requests and fully loaded in a little over 6 seconds.

The SAPUI5 Core gets loaded first and ensures a parallel loading of all required libraries. In addition, the browser loads all required CSS files in parallel.

Still, the time it takes to start loading the application is rather high – let’s look at some details of the request performance.

Notice the entry for “Waiting (TTFB)” – that’s Time To First Byte. This delay of around 194ms is basically caused by the high network latency since I am running this particular application on a server halfway around the world.

In my initial example, the customer was hit by a latency of close to 500ms – for each request!

Step 3: Use the AKAMAI network to reduce latency effects

In order to ensure that all static SAPUI5 files are served with the lowest possible latency, SAPUI5 has teamed up with AKAMAI and provides SAPUI5 from the HANA Cloud Platform as a Content Delivery Network cached by AKAMAI.

Here’s how to use it: Simply reference SAPUI5 from HCP (more on this here)

    <script id="sap-ui-bootstrap"
        src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js"
        data-sap-ui-libs="sap.m, sap.ui.table, sap.ui.commons, sap.ui.ux3"
        data-sap-ui-theme="sap_bluecrystal"
        data-sap-ui-compatVersion="edge"
        data-sap-ui-preload="async"
        data-sap-ui-resourceroots='{"LatencyDemo": ""}'>
    </script>

With this in place, let’s check the network trace again:

28 requests in a little over 4 seconds – and a low latency of only 34ms. Hooray!

This is already much better and will perform significantly better when the connection’s latency is even higher than in my scenario. AKAMAI will always server all SAPUI5 related content from the nearest possible mirror.

Step 4: Optimizing the application resources

So far, we have only optimized the way our application loaded the static SAPUI5 content. Looking further down the network trace, we still see quite a high number of requests and the typical waterfall pattern:

Let’s have another look at what is really happening. We can see that all application files are loaded sequentially which again is a performance anti-pattern.

The entire loading sequence is started with the following statement:

jQuery.sap.require('LatencyDemo.util.File1');

This synchronous API tells SAPUI5 to ensure that a particular file or module is available to be used within the application code. The module is either already known to SAPUI5 (then nothing happens on the network) or is it not. In the latter case, SAPUI5 will now issue a synchronous request to load this file.

In this particular case however, the contents of this file include the following line:

jQuery.sap.require('LatencyDemo.util.File2');

Upon receiving this file, SAPUI5 no needs to go out and fetch the next file – again a synchronous request. This can go on for quite some time depending on the dependency chain and its size.

Component Preload for Application Resources

SAPUI5 offers several ways to overcome this issue. All are based around the idea to bundle all of your application resources into one single file. This file can then be loaded early on to provide the content of all modules used within your application. Subsequent calls to jQuery.sap.require will now no longer result in requests to the server.

You currently have several ways to generate such a preload bundle:
– Using OpenUI5 tooling (see https://github.com/SAP/grunt-openui5#openui5_preload )
– Using the SAP Web IDE

In order to benefit from this preload, you need to ensure that you load the component.

Here is the updated code in the index.html file:

    <script>

		sap.ui.getCore().attachInit(function(){
    		jQuery.sap.require('LatencyDemo.util.File1');

			new sap.ui.core.ComponentContainer({
            	name : "LatencyDemo"
        	}).placeAt("content");
	  
		});

    </script>

Notice how we now create a new Component Container (which in turn will load the corresponding component via the right name). This component now loads the component-preload file from the server.

Generating a component preload using the SAP Web IDE

You can easily create this file using a particular project setting in your SAP Web IDE. Simply go to the project settings of your application via the context menu:

Go to “Project Types” and select “SAPUI5 Client Build”

Save and close the settings.

The preload file will be generated when the application is deployed to the Hana Cloud Platform. You can trigger the deployment via the context menu.

Let see the network trace:

Notice that the component-preload file is loaded. But wait, there are still the requests to File1 and File2 sent across the network. Why?

 

Step 5: Using the init event of the Component

The content of the preload file is loaded via an asynchronous call. Thus, similarly to the loading of libraries from SAPUI5, we also need to ensure that a usage of the included modules is done after SAPUI5 has loaded this preload bundle. For libraries we used the attachInit event of the SAPUI5 core, for application a similar point in time if the init function of the Component itself (see Component.js).

		init: function() {
			// call the base component's init function
			UIComponent.prototype.init.apply(this, arguments);
			
			jQuery.sap.require('LatencyDemo.util.File1');

			// set the device model
			this.setModel(models.createDeviceModel(), "device");
		}

Moving the jQuery.sap.require call into this function, we can achieve the desired result.

We also need to remove the call from the index.html:

    <script>

		sap.ui.getCore().attachInit(function(){

			new sap.ui.core.ComponentContainer({
            	name : "LatencyDemo"
        	}).placeAt("content");
	  
		});

    </script>

(Don’t forget to deploy the application again to regenerate the preload file).

Here’s the new network trace:

Notice: No application files are loaded any more. All views, controllers and other javascript files are fetched via the preload. Total time around 2s, 22 requests.

Conclusion
Our final application is now down to a load time of about 2s – while the preload file is still being loaded from a server halfway around the globe with a latency of close to 200ms. The overall loading time was reduced from 8 to around 2 seconds.

Here are the main improvements and key take aways:

  1. Always load all SAPUI5 libraries asynchronously
  2. Ensure the use the attachInit event before requiring SAPUI5 modules
  3. Use the AKAMAI network via the SAP HCP deployment if possible in your scenario to reduce the effects of latency
  4. Use application resource bundles
  5. Ensure to use the init event of the component before requiring additional application resources

I hope this guide contains some information which will help you to revisit your application and hopefully achieve a better startup performance as well.

Here’s the link to the second post on advanced topics:

SAPUI5 Application Startup Performance – Advanced Topics

To report this post you need to login first.

16 Comments

You must be Logged on to comment or reply to a post.

  1. Mike Doyle

    Thanks so much Frederic for taking the time to get this down in blog form.  Your session at Tech Ed was a highlight for me, so it’s great to have this blog as a reference.

    (0) 
  2. Iemke Kooyman

    Thanks for your insight into this subject. Great blog post which is going directly into my favorites folder as a reference document for all my SAPUI5 app development!

    (0) 
  3. Matt Harding

    Great post Frederic – type of enterprise thinking we need to see in more posts!

    Now I have a question for those who are predominantly doing Fiori Launchpad developments on 1.38 or higher, using WebIDE to deploy new apps and potentially have a regional Gateway server that FLP runs on…
    It looks like the main take away is the following line in the init of the Component file. Is this correct (since everything is else is taken care of)?

    jQuery.sap.require('LatencyDemo.util.File1');

    That said, if you use Asynchronous Module Definition (AMD) syntax in your code; should that be effectively (current as of today) best practice for your Fiori apps without needing to do this?

    Cheers,
    Matt

    (1) 
    1. Frederic Berg Post author

      Hi Matt,

      thanks for your comment! For applications which run primarily in the FLP context, there is another set of best practices which I will also write about. This post is simply the baseline which should be known to all developers.

      When it comes to AMD syntax, the code I used above is outdated. I did this to reflect a lot of the code which is still present in applications today and to provide a way to still optimize it easily.

      AMD syntax is of course the standard way of declaring and requiring modules nowadays, this will also be reflected in the next post.

      Thanks again
      Frederic

      (1) 
    2. Helmut Tammen

      As far as I understand the documentation SAPUI5 does support AMD syntax but does not load required modules asynchronously.

      Excerpt from sap.ui.define

      sap.ui.define is designed to support real Asynchronous Module Definitions (AMD) in future,
      although it internally still uses the the old synchronous module loading of UI5.
      Callers of sap.ui.define therefore must not rely on any synchronous behavior that they might
      observe with the current implementation.

       

       

      (0) 
  4. Stefanus Z

    Hi there,

    I’m trying to use the Component-preload to speed up the app.

    I have successfully created the file and I can see from my network watcher, it loads it first successfully and it contains all the files within my webapp folder.

    The problem is, it seems I still see the same amount of request being made?

    Do anyone encounter the same problem?

    Thanks!

    (0) 
    1. Frank Weigel

      The most typical reason for that is a mismatch between the module names that the WebIDE build determines at build time and the names that are used to require the modules at runtime.

      The Component-preload.js stores each module by its determined build-time name. At runtime, the framework looks the module up in the Component-preload.js with the name used at runtime. If both names don’t match, the preload won’t work and the framework will fallback to single requests.

      I would suggest that you take a look into the generated Component-preload.js and see if the module names look familiar to you. If not, most often, a mismatch is caused by the use of sap.ui.localResource or jQuery.sap.registerModulePath in the code of the app. These methods allow a quite flexible mapping from module names to source folders, but the WebIDE tooling doesn’t take these calls into account (the code analysis that would be necessary to do this would be rather complex and error-prone).

      HTH, Frank

      (1) 
  5. Mike Doyle

    Had chance to work through this in detail today with a legacy UI5 application.  I’m using the personal edition of Web IDE with an on-premise ABAP box acting as UI5 repository.  It seems that the personal edition doesn’t have the build option in the project settings?  I guess I will still have to use Grunt for the build.

    Also, when it comes to loading the libraries, I’m using the UI5 cache buster and the following reference

     src="resources/sap-ui-cachebuster/sap-ui-core.js"

    Doesn’t that rule out the use of AKAMAI?

    Thanks again for a great blog and I’m looking forward to the ‘Fiori edition’!

    (0) 

Leave a Reply