EDUCAÇÃO E TECNOLOGIA

UI5ers Buzz #50: The Loading Evolution: “Get the most out of your UI5 app!”

Introduction

On Valentine’s Day, 2020 at the UI5con in Belgium, I presented a topic I named “Loading Evolution” in the keynote. I wanted to demonstrate that the start-up performance of standalone UI5 applications is much better than its reputation. The start-up performance of web applications is very important. Maybe you also read the Web Fundamentals guide about Performance? It makes clear that the start-up performance is important to bind our users. The improvement of the start-up performance of UI5 applications is also one of the key priorities of the UI5 Evolution project and the best way to improve it is a reduction of the bytes to be loaded!

UI5 Evolution

With the UI5 Evolution project, we are investing in the modularization of the framework to have a more fine-granular module set, innovate the build tooling to be able to create smaller or even custom bundles, move towards DOM-based rendering to benefit from DOM patching and to get rid of the code for “custom setters” which were used in controls to optimally update the DOM without re-rendering. Unfortunately, due to compatibility reasons, we cannot just change the behavior of the UI5 framework and thus we need to rollout the information via documentation, blogs (e.g. Performance Checklist for UI5 Apps) or with the help of the UI5 Migration Tool that applications can finally adapt.

Performance Matters, …

…but please compare apples with apples!

Most of the time, when UI5 applications are compared with applications built with other UI frameworks my hackles raise. A UI5 application based on one of our application templates is by default responsive, globalized, customizable, accessible, secure, and supports theming. These qualities are provided by the UI5 framework and the controls (out of the box), but also coming with an additional price tag: more bytes (compared to other frameworks)!

In future, the modularization of the UI5 framework will help to safe more bytes by loading only the needed functionality for the UI5 framework and include only the necessary controls as fine-grained as possible. But hey, even today, a UI5 application can reach good performance numbers!

Let’s challenge this together! The goal is to get the most out of your app without rewriting the application internals – just by using different bootstrapping mechanisms: preload vs. self-contained vs. progressive. You can also replay this by yourself. Just follow the instructions in this blog (as prerequisites Node.js 10+ and the Git CLI is required).

The Challenge

We will use the OpenUI5 sample application and compare the loading performance using a preload bundle with a self-contained bundle and finally with a progressive bundle. We will not rewrite the internals of the application. To measure the performance we will use the Lighthouse tool in Chrome to audit our application and try to reach the best possible performance score on mobile devices with simulated throttling.

First, we need to setup the OpenUI5 sample application locally, since we will use it to test the different bundle variants and to measure the performance. So let’s clone the repository with the Git CLI:

git clone https://github.com/SAP/openui5-sample-app.git

Switch into the folder of the cloned repository and ensure to checkout the proper commit this blog relates to:

git checkout 2d3e24187c8992cc7e0c6ecf940dfb675a07dd68 -b challenge

Now we need to prepare the project for the first run. Therefore, we switch into the folder of the OpenUI5 sample app and install the project dependencies via the command:

npm install

Once the installation of the project dependencies is completed, we can now start the OpenUI5 sample application with the command:

npm start

The start script runs the development server and we can open the application with http://localhost:8080/index.html?sap-ui-language=en (a solution to declare the supported locales of UI5 applications in the manifest.json is in the pipeline, for the time being we are using the URL parameter…)

If you open the “Network” tab in the Chrome browser and reload the application, you will see a lot of individual requests to the individual modules of the UI5 framework. This is the development mode and best for local or framework development. But it doesn’t make sense to measure the performance of the application when running in the development mode.

Let’s test the application in a productive mode by using the different bundles: preload, self-contained and progressive.

Preload Bundle

The preload bundle for the OpenUI5 sample application can be created by running the UI5 build. Just execute the following command:

npm run build

The build script runs the ui5 build with the option to also build the dependencies (you can see this in the package.json). The build will copy the application resources and e.g. create the Component-preload.js bundle in the dist folder. In addition, all required libraries will also be built and put into the dist/resources folder. So, a static webserver can be used to run the application from the dist folder. The OpenUI5 sample app already has a script for this. You can run the webserver with:

npm run serve-dist

The serve-dist script starts a static web server and we can open the application with http://localhost:8000/index.html?sap-ui-language=en

Now we can measure the performance of the UI5 application running with the preload bundle. Therefore, we can open the Chrome developer tools, navigate to the “Audits” tab and deselect all categories besides “Performance” and as device we select “Mobile”.

Now we can click on the button “Generate report”. As a result of the audit we will see the following result:

Looking into the network trace we can see the following figures about the application:

Performance 32, 15 requests and 1.4 MB of content being transferred.

Using the preload bundles is the default loading behavior of UI5 applications. It allows to load the preload bundles asynchronously (if the bootstrap option is enabled, see in the Performance Checklist).

When running a UI5 application in the SAP Fiori launchpad, the loading performance of the application can benefit from already preloaded libraries which are declared in the manifest.json. Also, the SAP Fiori launchpad itself preloads a lot of the required libraries and shares them with the applications already.

Standalone UI5 applications do only partially benefit from the library preloads. The library preloads are coarse-grained and most of the applications only use a small subset of them. Only in the CDN scenario, the library preload again becomes interesting as the library preload bundles could be already in the browser cache. But for standalone UI5 applications – especially in mobile scenarios in which caches are transient – each byte counts. Therefore, we can tailor a self-contained bundle by using the UI5 Tooling.

Self-Contained Bundle

The self-contained bundle for the OpenUI5 sample application can be created by running the UI5 build by executing the following command:

npm run build-self-contained

After the build we can run the generated application with the following command:

npm run serve-dist

Now we can open the application with http://localhost:8000/index.html?sap-ui-language=en.

After generating the report for OpenUI5 sample application running with the self-contained bundle, we see the following performance score:

Looking into the network trace we can see the following figures about the application:

Performance 57, 10 requests and 860kB of content being transferred.

The nice thing about this performance boost is, that it mostly comes for free. You just run the self-contained build for your standalone UI5 application and the UI5 Tooling is able to detect the required modules and tailor an optimal bundle. In addition, the UI5 Tooling is modifying the index.html and changes the bootstrap to load the sap-ui-custom.js bundle instead of sap-ui-core.js.

But even this is still not good enough. To reach the next level of performance we need to think about a progressive loading and create a progressive bundle for the application. This now is the black-belt mode of using the UI5 Tooling. If you master the bundling, it allows you to orchestrate the loading sequence of your UI5 application!

Progressive Bundle

To build a progressive bundle for the OpenUI5 sample application we need to enhance the application bootstrap now. As said initially, we will not modify the application internals which would be a bit more advanced. We will just split the self-contained bundle into the bootstrap and the bundle for the component.

First, we need to modify the index.html, the ui5.yaml and define our new bootstrap in a new module which is used for the UI5 Tooling to create the new bootstrap script.

Let’s start to define our source file for the bootstrap bundle. Therefore, we create a file called Boot.js next to the Component.js in the webapp folder. The content of the file looks like this:

sap.ui.define([ "sap/ui/core/Core", "sap/ui/core/library", // avoid library preload of sap.ui.core "sap/m/library" // avoid library preload of sap.m ], function(Core) { "use strict"; // preload the library resources bundles async // which happens automatically for library preload Promise.all([ Core.getLibraryResourceBundle("sap.ui.core", true), Core.getLibraryResourceBundle("sap.m", true) ]).then(function() { // boot the Core: // - loads the Component-bundle defined in data-sap-ui-modules // - using the ComponentSupport in sap-ui-onInit to load the declared component Core.boot(); }); }); 

The bootstrap requires the Core as well as the necessary libraries to suppress the library preload when the component factory loads the component. Additionally, the bootstrap should take care to preload the resource bundles of the required libraries asynchronously (which usually happens together with the library preload) and finally boot the Core.

The next step is to instruct the UI Tooling to create the bootstrap bundle. Next to this bundle we also need to create the component bundle which should contain all resources which are required fro the component but not included in the bootstrap bundle. This will be done by defining custom bundles in the ui5.yaml. Open the ui5.yaml and add the following content at the end. Just grab the builder section after “# […]” at the end of the file:

specVersion: '2.0' metadata: name: openui5-sample-app type: application # […] builder: bundles: - bundleDefinition: name: custom-boot.js defaultFileTypes: - ".js" sections: - mode: raw filters: - sap/ui/thirdparty/baseuri.js - sap/ui/thirdparty/es6-promise.js - sap/ui/thirdparty/es6-shim-nopromise.js - ui5loader.js - ui5loader-autoconfig.js resolve: false sort: true - mode: preload filters: - sap/ui/demo/todo/Boot.js resolve: true sort: true - mode: require filters: - sap/ui/demo/todo/Boot.js bundleOptions: optimize: true usePredefineCalls: true - bundleDefinition: name: sap/ui/demo/todo/Component-bundle.js defaultFileTypes: - ".js" - ".json" - ".xml" - ".properties" sections: - mode: provided filters: - ui5loader-autoconfig.js - sap/ui/demo/todo/Boot.js resolve: true - mode: preload filters: - sap/ui/demo/todo/Component.js - sap/ui/demo/todo/manifest.json - sap/ui/demo/todo/controller/** - sap/ui/demo/todo/i18n/** - sap/ui/demo/todo/view/** resolve: true sort: true bundleOptions: optimize: true usePredefineCalls: true 

As you can see, we specified the following two bundles in the ui5.yaml: custom-boot.js and sap/ui/demo/todo/Component-bundle.js. To get an understanding what these bundle definitions are doing, let me briefly explain it here:

custom-boot.js: will be created at dist/resources/custom-boot.js

The custom-boot.js includes .js files only (as defined in defaultFileTypes) and consists of non-UI5 modules (which are defined in the raw section) and UI5 modules (which are defined in the preload section). The raw section includes all files relevant for the UI5 module loader and in the preload section we reference the Boot.js module with its namespace and set the option resolve: true. This option will take care that all required dependencies of the Boot.js will be included in this bundle. The last section is the require section. Here we also list the Boot.js with namespace to ensure that the bundle requires the Boot.js module and the code will be executed once loaded.

sap/ui/demo/todo/Component-bundle.js: will be created at dist/Component-bundle.js

The Component-bundle.js uses the namespace of the Component which is mapped to the dist folder. That’s the reason why this bundle is not created inside the dist/resources folder as the custom-boot.js above. The Component-bundle.js includes .js, .json, .xml and .properties files. It defines two sections: the provided section which lists the modules that should not be included in the bundle (by using the option resolve: true also the dependencies of those modules will be excluded) and the preload section which includes all relevant resources from the Component (must be defined with the namespace). The option resolve: true ensures that the required dependencies for the Component will be also included in the Component-bundle.js.

If you are interested to understand the options of bundle definitions in more detail, check out the Custom Bundling section in the UI5 Tooling documentation.

The build can now produce our custom bundles and we need to reference them in our HTML page. In order to use the custom bundles, we create a copy of the index.html and name it index-bundle.html. The content of the index-bundle.html have to look like this:

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>OpenUI5 Todo App</title> <script id="sap-ui-bootstrap" src="resources/custom-boot.js" data-sap-ui-theme="sap_fiori_3" data-sap-ui-resourceRoots='{ "sap.ui.demo.todo": "./" }' data-sap-ui-modules="sap.ui.demo.todo.Component-bundle" data-sap-ui-onInit="module:sap/ui/core/ComponentSupport" data-sap-ui-compatVersion="edge" data-sap-ui-async="true" async> </script> </head> <body class="sapUiBody"> <div data-sap-ui-component data-name="sap.ui.demo.todo" data-id="container" data-settings='{"id" : "todo"}'></div> </body> </html> 

In the index-bundle.html we changed the bootstrap script to use our custom-boot.js:

 src="resources/custom-boot.js"

The attribute data-sap-ui-modules was added to ensure that the Component-bundle is being loaded when UI5 boots:

 data-sap-ui-modules="sap.ui.demo.todo.Component-bundle"

This could be also implemented as part of the Boot.js above (using sap.ui.require inside the module) but this would be more verbose than the declarative option here.

Finally, we are ready to test the progressive bundle. Compared to the self-contained bundle it is not one big JavaScript file anymore but split into the bootstrap and the Component-bundle. We can run the build by calling:

npx ui5 build

After the build has completed, in the dist folder we should find our index-bundle.html, the Component-bundle.js and inside the resources folder the custom-boot.js. Once these files are available, we can start the application again with the static web server with the following command:

npm run serve-dist

Now we can open the application with http://localhost:8000/index-bundle.html?sap-ui-language=en.

After generating the report for OpenUI5 sample application running with the progressive bundle, we see the following performance score:

Looking into the network trace we can see the following figures about the application:

Performance 71, 11 requests and 746kB of content being transferred.

As you have seen, the progressive bundling requires additional configuration and some more expert knowledge. On the other side, we have more freedom to orchestrate the bootstrap. The code in the Boot.js is executed very early and could be used to interact with the DOM (e.g. showing a splash) or you could require UI5 modules/controls and render them early before the Component is finally loaded.

Comparison

The following table compares the performance results from Lighthouse and the Network tab in Chrome measuring the mobile scenario with simulated throttling for the preload, self-contained and progressive bundles:

  Preload Self-Contained Progressive
Performance 32 57 71
Requests 15 10 11
Size 1.4MB 860kB 746kB

This is what you can get more or less out of the box, without reworking the internals of the application and finally we could double the performance score by just utilizing the loading evolution capabilities.

Let’s cheat a bit… 😉

Another simple approach to improve the perceived performance of our application is to include a splash screen in the beginning. It will give the user the impression that the application loading is already in progress. Just put the following snippet into the head of your index-bundle.html:

 <style> html { height: 100%; } body { display: inline-block; background-image: url('https://www.sap.com/dam/application/shared/logos/sap-logo-svg.svg.adapt.svg/1493030643828.svg'); background-position: center; background-repeat: no-repeat; } </style> 

An inline SVG as background image would be even better, but if we now audit the UI5 application, we even get additional more 10 points and reach the performance score of ~80.

Conclusion

As we can see, UI5 applications reach quite good performance scores on mobile devices. Even without rewriting the internals of the application, just by optimizing the loading performance we doubled the performance score. To get even more out of it, we would need to touch the application. Still, we from UI5 know that we need to continue to work on the reduction of the framework size. Especially on mobile devices with simulated throttling the size is an essential factor as caches are transient. Keep in mind, applications built with UI5 already come with a lot of qualities as they are by default responsive, globalized, customizable, accessible, secure and supports theming. Building enterprise-grade applications require those qualities and this comes with a price tag. But I am sure that this challenge shows that UI5 is heading into the right direction. Stay tuned…

Previous Post: UI5ers Buzz #49: The UI5 Tooling and SAPUI5 – The Next Step

Peter is one of the initiators who started the Phoenix project (a.k.a. OpenUI5/SAPUI5) grown into the role of the UI5 Chief Architect mainly focusing on UI innovation in the area of the UI framework (UI5 Evolution), controls (UI5 Web Components), tooling (UI5 Tooling).