Skip to Content
Technical Articles

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). 

 

 

 

22 Comments
You must be Logged on to comment or reply to a post.
  • how about DOM manipulation ? Will UI5 be still using jquery here or move to virtual dom engine. Above tests nicely demonstrates the loading performance but how about routing. UI5 simply hides to view when moving between routes, when you inspect the elements, you can still see your view but hidden. Will there be any change regarding to this ? I still feel performance of UI5 is terrible, it is not about loading.

     

    • That’s what I briefly mentioned in the section about UI5 Evolution in this blog. UI5 is moving towards DOM manipulation. The rendering has been extended to support DOM patching instead of re-rendering the HTML of the complete control. This is the Semantic Rendering (or the Rendering V2). Controls are being migrated to that semantic rendering. Instead of using VDOM the semantic rendering itself knows how to do DOM patching optimally. This is the video about Semantic Rendering from the last UI5con 2019 where we announced our move towards Semantic Rendering: https://youtu.be/lPETbnuL9hs?t=3934 (expert session with details: https://www.youtube.com/watch?v=gNYQO3F6M2E). jQuery is not used for DOM patching and we are more and more removing jQuery out of the Core layer of the UI5 framework.

      Regarding routing behavior: IMO this depends on the scenario. If you assume to navigate often between your views, its better to keep them so that the navigation between the views is fast. Then there is no need to recreate the controls and instantiate the controller logic again. But in case you are rarely using the views, throwing them away would be much better. Considering that the views are most often created in context of a component, the views keep stacked as long as the component and the router inside lives. Once the component is destroyed, then also the views behind the router get destroyed. I will take your point and discuss it with the experts – maybe this could be an option to configure to avoid the stacking.

      One thing I noticed in the past which often caused performance issues with views while navigating between them wasn’t related to the stacking of the views. More often it was related that the apps didn’t use the async flag for routing and the views have not been preloaded in the Component bundle. Thus during routing sync requests took place to load the views and then this completely messed the app since it simply blocked. The point 3) in the following blog https://blogs.sap.com/2020/02/06/ui5ers-buzz-47-performance-checklist-for-ui5-apps/ . Maybe this is the reason about the negative performance feeling you have.

      • thank you for detailed explanation. in my current company, we created several planning application by using UI5. I can easily say, users were planning to drop SAP totally from the planning environment and we change the way of user interact with the system by using UI5. Last time they were using an add-in called EPM Add-in ( for SAP BPC ) now they use pure UI5 for data input. Now we have so many projects just because of succesfull UI5 projects.

        We are using a lot of input screens, thousands of rows and 60 to 100 columns in total. Our main problem was not loading. especially CDN was making it faster. pre-load yes a bit but it is not noticable. Our problem is mainly on routing and rendering for big tables. Since we have 30+ page in the application and you can notice the terrible performance after you visit 6-7 view. Another thing is how sap.ui.table is rendering the rows and columns. For row rendering yes you can see they only render visible rows (but not smooth) but column rendering is terrible again.  Page was moving bit by bit. We had to drop ui.table and switch to some third party input controls. Do you think DOM manipulation will make ui.table rendering faster ? Especially if you have more columns.

        I checked the link you gave and will try some tricks on our application but i have worries about the performance. To be honest, i am planning to use some VUE applications in an iframe inside the SAPUI5 application to hide the performance issues 🙂 I made a POC and everyting is fine . I have serious concerns about future of ui5 regarding performance. We are not expecting cool ui5 controls but the basic performance without going deep and doing some hidden tricks where only 50 ui5 developers can know in the world. Majority of SAPUI5 developments are playing around very simple and primitive ERP scenarios. Approvals, some list views of transactions, workflows, image views etc.  I feel dejavu. Entire scenarios looks repetitive and primitive.

        SAP did good job on SAP Analytics Cloud UI5 controls but their controls are not public, very bad.

        Thank you.

        • Thanks for your feedback, Bilen. The positive in the beginning and also the critics afterwards.

          Can you please share some infos about your apps with me: Which version of UI5 are you using? How many rows do the tables typically display on the screen? What kind of controls are used inside the table? Do you use the async flags for the root view and the router?

          I can say that we are taking the performance topic seriously and we are working on it to improve it. The critics are important to learn from you and that’s why I am happy that you are that honest with me. I will take it and try to address it.

          Regarding the Table performance: the column are not virtualized like the rows and this is indeed bad if you have a lot of columns. Having many columns will for sure affect the performance of the table. The Semantic Rendering will help when scrolling/updating the Table but also for the initial rendering there will be no difference compared to the previous rendering (the rows and cols need to be created and rendered!). Interesting would also what happens behind the scenes the table performance is not only the visual part – it is also the data model and the logic underneath.

          Your scenario is interesting: in your UIs you have many views which include big tables and you are navigating between the views. The router should be used async this is essential, otherwise the UI stucks while navigating between the views (if new views are created and the resources need to be loaded). I will try to make a POC on that so that I can look into this to understand this scenario a bit more.

          Just an idea: as the sap.m.NavContainer stacks the pages and I checked whether this can be suppressed via a property – unfortunately not – as an alternative, what if you would do a similar trick like displaying the Vue application in the iframe (yes, not really similar, but…) – you could think of just call setVisible(false) in the routeMatched and setVisible(true) in the beforeRouteMatched event? I know, this is tricking but not more than using Vue inside. This would clean up the DOM of the tables in the hidden views – at least for the time being until a better solution from the framework comes?

          I fully accept your concerns about the basic performance without going deep into the tricks.

           

        • Understood it – even without the trick of setVisible for the Tables – I suggested before – you could increase the performance of the routing. I rebuilt your scenario locally with 5 views containing each a sap.ui.table/Table control with 20 rows / 100 columns and in the data model I have 100000 rows. The table uses mainly the sap.m/Input control. I noticed as you said that the navigation between the views is slow – if the view has been created newly it was even slower. After looking into the Performance tab while navigating between the views I noticed a very long relayout task (and the bigger the DOM is the longer it takes). The reason for the relayout is the navigation transition of the NavigationContainer which is by default “slide” (which looks nice from UX perspective but comes with a price tag – which is huge in case of big tables). To suppress this very long relayout task in case of such huge screens with the table an easy solution is to use the defaultTransitionName=”show”. Just by changing the transition in my app the navigation between the views even without tricking was much faster and no very long relayout was visible in the Performance tab. 

          Thanks for that information! 🙂 Definitely, there could be much better solutions, e.g. virtualization of the table colums, or less DOM but reducing the DOM is a topic for itself (due to ACC and UX requirements). I will take this scenario with me for performance discussions…

          • Hi Peter,

            Thank you for answers, really appreciate it. I was not expecting such detailed answer.

            Let me show our architecture of the system and sample screenshot of our apps. First of all we use only 1 oDATA service (Created 3 years ago and never touched after that) for around 200+ endpoints. We heavily use strategy design pattern for this approach for a clean architecture. It is a simplified oDATA service, looks like more RESTified for JSON communication only. So no annotations, deep inserts or any other oDATA specific complicated things.  Let me show the diagram;

            So our oDATA service URL looks like during call;

            /ZSERVICE/DATASet(TMPL_ID='REPORT',METHOD='GET_SUMMARY')

            Entire approach is playing with URL parameters and deciding what BADI to trigger in the backend.

            This approach gives us a lot of advantages from tracking the requests and preparing a analysis report on performance on year end and also flexibility during development.

            So any ABAP developer does not need to know the oDATA part. They need to have strong algorithm and ABAP experience. Here comes the beauty of strategy design pattern. Entire logic is not disturbed by the complexity of request. All BADI implementations are isolated but triggered from same point with same interface. It gives us flexibility of several developers can work on same thing and also we can easily move outputs from one class to other class easily. Also we can decide the parallelism easily by splitting logics into different methods/classes. Highly scalable and flexible.

            Finally they just return a data reference which can contain anything. Handler classes are converting them back to JSON automatically. So on UI5 layer, we just need to capture our JSON output which always comes from the same field named “CONTEXT“. This is the fieldname i am using during POST request to send parameters and also receive result. During a POST request, ABAPer only need to know the structure to create same internal tables on ABAP layer.

             

            So we are imitating the graphQL technical approach for architecture wise (primitive one hehe). Mainly using POST request even it can be done via GET request. More flexible and no length limitation.

            We are not using $top, skip , limit etc. Because our datasources are not tables, they are infocubes so we have read all filtered data from frontend at once. Reading a big data and moving it to frontend is not really a problem, that waiting time is ignorable. 

            Below is some screenshot from our application, one is from input form and menu (sorry i had do blur).

            We are using sap.tnt library for menu on the left. similar to sap cloud platform menu. This was the input control i was talking about. I hide the left part contains the master data description but input has 100 col+ here. With sap.ui.table it was really hard to render and switch between icon tabbars. I used a different library. I am not very happy but at least copy-paste coming out of the box and rendering is very fast.

             

             

            So we have a left menu with linked to several pages. In the middle, each view is rendered.
            Another screenshot from different project homepage. Here we used some other css for the landing page.

            Approach is same, an admin-style template with left menu bar and views are rendered in the middle. I will try your routing parameters, i know definitely it will help.

            We have another problem on memory especially JSON models declared in page. I understand view is hidden in DOM but JSON model also stays in the memory if you don’t clear them before you leave the page.

            onInit: function() {
                this._oView = this.getView();           
                this._oView.addEventDelegate({
                    onBeforeHide: function(oEvent) {
                      //clear JSON models here
                    },
            
                    onAfterHide: function(oEvent) {
                        // maybe destroy here ?
                    }
                }, this)
            }

            I will implement above logic to clear the json models, else memory is growing on chrome task manager. I can understand this.setComponentModel will live on memory but is it view model also live forever because view is not destroyed but hidden?

             

            Last question is, if you go through the component library files, you can still see JQuery is heavily used. Especially get/set component styles, can this create performance issue ? ( as of 1.73)

            sample:

            sap.ui.define([
            	'./FlexBoxStylingHelper',
            	'./FlexItemData',
            	'./library',
            	'sap/ui/core/Control',
            	'sap/ui/core/InvisibleRenderer',
            	'./FlexBoxRenderer',
            	'sap/ui/thirdparty/jquery'
            ],
            function(
            	FlexBoxStylingHelper,
            	FlexItemData,
            	library,
            	Control,
            	InvisibleRenderer,
            	FlexBoxRenderer,
            	jQuery
            ) {
            /// some codes here
            if (oItem.getLayoutData()) {
            			oWrapper = jQuery(document.getElementById(oItem.getLayoutData().getId()));
            		} else {
            			oWrapper = jQuery(document.getElementById(InvisibleRenderer.createInvisiblePlaceholderId(oItem))).parent();
            		}

            Why i have concerns on performance is, for an ordinary SAP GUI user, UI5 is a big evolution and revolution for SAP. Our planning users are not using SAP GUI at all and they are heavily using React and Angular apps for their internal applications. We really have big competitors in the company :).

            Thanks again for the answers, i will try your advises on Monday morning and also the link you pass me previously.

            Have a nice weekend.

             

          • Hi Bilen,

            thanks for this detailed explanation of your scenario. This helps a lot to understand your situation. The idea with the single endpoint and reusing it for different screens is pretty interesting. Let me first answer a bit more in general and afterwards, I will answer your questions. Sorry that my answer is that long… :-/

            Before I start, I need to write some words about the overall performance of UI5 and the comparison with other frameworks like React, Angular or Vue (remember my hackles raise 😉 ): The performance of UI5 overall isn’t bad and we are working on making it even better step by step. We do that as fast as we can, but we have to stay compatible to not break existing apps. In your case, as far as I understand it, you mainly criticise the performance of individual controls and not the framework overall. To be fair, we have to compare apples with apples, frameworks like React and Vue are working different. They are much more basic than UI5 is from an overall feature perspective. React and Vue provide you some great tools and concepts to write your components, handling the events, connect the data with the components and do the optimal rendering for you. This functionality is also part of the UI5 framework, but UI5 comes with much more functionality out of the box (even if it is not needed):

            Especially features (independent from whether they are good or bad) like more advanced databinding (two-way, control binding syntax, …), the types and formatting (date, time, numbers, currency, …) and the programming model (MVC, UI5 Components, …) are on top of that what React and Vue are providing out of the box and you need to add additional packages to your app which bring in these features. Angular is closer to UI5 as it also provides you a complete framework to develop your application inside and you do not really need to take care to install additional packages to get your work done. But in any case, the biggest difference between UI5 and the other UI frameworks are the rich set of UI5 Controls (in other framework terminology: the components).

            And this brings me to your critics which are good, IMO, because we need to challenge UI5 in all dimensions:

            Let’s first talk about jQuery. The removal of jQuery doesn’t automatically mean that UI5 will have a better performance. jQuery was mainly used as a browser abstraction layer which also adds syntactic sugar on top of the browser APIs. With the browsers having evolved over the last years, this kind of abstraction isn’t needed anymore. Thus, we could save some kBytes by removing jQuery from our UI5 framework code base (controls might still need it, but this is a different story). Also keeping track with newer versions of jQuery is expensive and right now we are stuck with 2.2.3. As our customers expects compatibility, a switch to jQuery 3.x will break the current behavior and we noticed that in our tests. But we for sure fix the security issues in this jQuery 2.2.3 copy to ensure that the apps built with UI5 on top of that jQuery version are safe! IMO, the performance issues are not related to jQuery. More they are related on what is being done with jQuery. In the case of the routing and the transition of a big sap.ui.table/Table comes with a price tag. When removing the class to make the sap.m/Page with the sap.ui.table/Table visible, the browsers’ rendering engine needs to re-layout the screen. This is more expensive the more DOM elements are visible. In addition, after the sap.ui.table/Table becomes visible it starts the width and height calculation of the rows and columns which is also more expensive the more rows and columns are displayed and reading the sizes from the DOM enforces a re-layout of the screen. The sap.ui.table/Table control tries to optimize here as much as possible to avoid re-layouts taking place. The reason for all this is to be responsive on the sizes and the content. IMO, jQuery is not the root cause for the performance issue, it is more what the developers are doing with jQuery. But to get rid of jQuery will decrease the size of the UI5 framework and reduce the cost of ownership as we need to support the jQuery copy right now. IMO this is also as important as the performance

            Now, let’s talk about the performance of the sap.ui.table/Table control. This control focuses mainly on row-based mass data, but not on displaying a huge number of columns. It implements a lot of features like filtering, sorting, grouping, fixed rows and fixed columns, dynamic row heights and column widths, automatic row count detection (very expensive!), column resize and visibility, … The tables which are used in your scenario do not really require all those features. For sure, reusing the sap.ui.table/Table control would be the best approach but the performance for scenarios with a lot of columns isn’t optimal as the table is not virtualizing the columns and thus creates all the columns. To understand the negative impact of having many columns (and many rows) we need to look a bit into the internals of UI5, the databinding, the controls and the rendering. When a control like the sap.ui.table/Table is used, having a lot of columns defined, there happens a lot during the control creation internally. The table is using the template controls from the column definition to create a template for the rows aggregation. The rows aggregation template is being cloned for each visible row of the table. Assuming we have a table with 50 rows and 100 columns we immediately have created ~5200 managed objects (be it controls or elements). This is an advantage with databinding to update individual cells only and Semantic Rendering takes care to update the DOM of the cells. But for the initial rendering this is a disadvantage as a lot of managed objects needs to be created.

            In your scenario, the table control itself looks very lightweight. You display data, no input seems to be needed. Instead of using that feature rich sap.ui.table/Table control, a simple lightweight table control which just displays the data in an HTML table would be sufficient. If you would implement this as a UI5 control, it would be similar like developing a React or Vue component which is used to visualize the table data. You will get a two-dimensional array and then render the table control for that. This could look like this (very primitive, no warranty):

            my/SimpleTable.js

            sap.ui.define([
                "sap/ui/core/Control"
            ], function(Control) {
                "use strict";
            
                return Control.extend("my.SimpleTable", {
            
                    metadata: {
                        properties: {
                            data: "object"
                        }
                    },
            
                    renderer: {
                        apiVersion: 2,
                        render: function(rm, control) {
                            var oData = control.getData();
                            rm.openStart("div", control);
                            rm.class("mySimpleTable");
                            rm.class("sapMList");
                            rm.openEnd();
                            rm.openStart("table");
                            rm.attr("cellspacing", "0");
                            rm.openEnd();
                            rm.openStart("thead");
                            rm.openEnd();
                            rm.openStart("tr");
                            rm.class("sapMListTblRow");
                            rm.openEnd();
                            for (var col = 0, colCount = Array.isArray(oData) && Array.isArray(oData[0]) && oData[0].length; col < colCount; col++) {
                                rm.openStart("th");
                                rm.class("sapMTableTH");
                                rm.class("sapMListTblCell");
                                rm.attr("width", "150px");
                                rm.openEnd();
                                rm.text("Column " + col)
                                rm.close("th");
                            }
                            rm.close("tr");
                            rm.close("thead");
                            rm.openStart("tbody");
                            rm.openEnd();
                            for (var row = 0, rowCount = Array.isArray(oData) && oData.length; row < rowCount; row++) {
                                rm.openStart("tr");
                                rm.class("sapMListTblRow");
                                rm.openEnd();
                                for (var col = 0, colCount = Array.isArray(oData[row]) && oData[row].length; col < colCount; col++) {
                                    rm.openStart("td");
                                    rm.class("sapMLIB");
                                    rm.class("sapMListTblCell");
                                    rm.attr("width", "150px");
                                    rm.openEnd();
                                    rm.text(oData[row][col])
                                    rm.close("td");
                                }
                                rm.close("tr");
                            }
                            rm.close("tbody");
                            rm.close("table");
                            rm.close("div");
                        }
                    }
            
                });
            
            });

            UPDATE 1: improved the reuse of the sap.m/List classes to get the same styling (unfortunately the reuse of CSS classes from UI5 is not officially supported since we cannot guarantee compatibility but it adds theming – in future same could be done better by using UI5s css variables, hopefully another blog post soon…) 

            my/SimpleTable.css

            .mySimpleTable {
                overflow: auto;
                width: 100%;
                height: 100%;
            }
            .mySimpleTable thead {
                display: table;
                width: 100%;
            }
            .mySimpleTable tbody {
                display: block;
            }
            .mySimpleTable tbody tr {
                width: 100%;
                display: table;
                table-layout: fixed;
            }
            .mySimpleTable tbody td {
                display: table-cell;
                text-align: center;
            }
            

            In this case, UI5 is creating and rendering only one control and the semantic rendering should take care for proper DOM patching in case of updates. That’s why the renderer is written in the semantic rendering syntax (which looks like incremental DOM). The initial rendering would be also fast, as the databinding will not need to create many controls/elements for the rows and the cells. Using this kind of table in my POC shows a pretty good performance when switching pages. It comes with a small DOM hierarchy and a small UI5 Control hierarchy. Depending on your requirements to this control, such a notepad table control might be enough. As mentioned earlier, I accept the critics here and will see what is possible to improve the sap.ui.table/Table control in future or whether we provide a simple table controls for such read-only mass scenarios.

            In my last reply, I also sketched another idea to reduce the size of the DOM by using setVisible: false when navigating away. I now also took your idea to use the event delegate to hook into the beforeShow and afterHide event which I forgot that this exists (and it is easier than listening to the routeMatched event – thanks 🙂 ):

            onInit: function() {
            	this.getView().addEventDelegate({
            		onBeforeShow: function() {
            			console.log(this.getView().getId() + ": onBeforeShow");
            			this.byId("table").setVisible(true);
            		}.bind(this),
            		onAfterHide: function() {
            			console.log(this.getView().getId() + ": onAfterHide");
            			this.byId("table").setVisible(false);
            		}.bind(this)
            	});	
            },
            

            When navigating away from the page, the table will be hidden and the DOM content will be removed. Only a simple div placeholder will stay. But maybe the switch to the defaultTransitionMode: show will already improve enough and you do not need to change the visibility of the table.

            Regarding the JSONModel: neither the component model nor the view model gets destroyed even when the view or component will be destroyed. The model lifecycle needs to be managed by you. The reason for that is that model can be shared across different components, views or controls and the models don’t know about this.

            Your idea about freeing memory could work. If you clear the model data, the browser will at some point in time, when the garbage collection runs, also free the memory again. So besides hiding invisible tables, freeing the memory could also help to improve the performance. You can hook into the view lifecycle similar like you suggested or shown above to do a cleanup of the model data – but do this after the control is invisible to avoid a rerendering.

            Regarding your last question about jQuery: even if we remove jQuery from the framework, there might be still some controls which continue to use jQuery. As mentioned above, IMO it is not a performance problem to use jQuery – it is rather more an issue when you don’t know what the usage of a jQuery or DOM API does. E.g. if you access the sizes of the DOM element, this can trigger a relayout which could be pretty expensive in case of having complex DOM structures. Just retrieving elements by their id with the jQuery APIs isn’t a performance issue at all. IMO, in UI5 we are doing still too much JavaScript to align the sizes and position of the controls. All those resize handling and positioning by accessing the DOM to measure the sizes is pretty expensive and I would be more than happy if we could just omit this and do everything with CSS. But this contradicts the requirements UX has…

            Sorry for that long and detailed answer, but I wanted to give you also some more background on the individual topics.

            Have a great start into the week,

            Peter

            /
          • Thank you Peter!

            I got all my answers, i need to try and see the performance results first. Just a quick note, the control i pasted above was also for input purpose with copy-paste enabled from excel ( i will try your sample for read-only output). If you ask me if i had 1 request from SAPUI5 team what would it be is, an excel-like input screen like SAP Analytics Cloud have 😀

            Below you can find also formatting (yellow) after each cell change ( i implemented an event here)

             

            Thanks again for your time and very valuable information, really appreciate.

            /
          • hi Peter,

            i added below to my manifest.json and also changed all routing to show from, slide. Another thing routing-async also set to true now and there is a significant increase on the performance. During navigation no additional .js files are loaded (except controller itself) i think main impact is coming here.

            I tested Ui5-cli also on my local for preload, yes we load less .js file during navigation but performance change is not like as below. But still faster.
            Overall there is a huge improvement, thank you.

             "dependencies": {
            			"minUI5Version": "1.70.0",
            			"libs": {
            				"sap.ui.core": {},
            				"sap.m": {},
            				"sap.tnt":{},
            				"sap.f":{},
            				"sap.ui.table":{},
            				"sap.ui.unified":{},
            				"sap.ui.layout": {
            					"lazy": true
            				}
            			}
            		},

            only thing CLI is giving below error but still generating the files;

            (node:4130) UnhandledPromiseRejectionWarning: TypeError: Cannot read property '$ns' of null

            if i put all the views in views folder it is fine. if there are nested folder somehow giving error. 

          • Hi Peter,

            Yes there was a namespace issue now it is fixed . I also tried CI/CD with Azure DevOps using UI5-CLI and works very well. Only thing is it does not push to SAP server but still okay . I can get final clean version of production ready with one click using CI/CD.

            Thanks again.

             

  • Hi Peter,

    thank you for hard work and excellent blogs. I am always excited to read them.

    I started upgrading my personal PWA App to ui5 tooling 2.0 and wanted to increase the performance.

    Unfortunately I am already running into an error when I use

    ui5 build self-contained -a --clean-dest

    that I can’t quite explain:

    Here is the code (which is running perfectly fine when running ui5 serve -o index.html:

    _getWasteItemsFromModel: function () {
      var oModel = this.getView().getModel("waste_items");
      return oModel.getProperty("/wasteItems").map(function (oWaste) { return Object.assign({}, oWaste); });
    },

    As the ui5 sample app is more or less doing the same the only difference I can see is that my function is getting called inside onAfterRendering (to add chart.js based chart).

    onAfterRendering: function () {
          var ctx = document.getElementById("barChart");
          this.aTotalWasteData = Wastecalc.calculateTotalWasteValues(this._getWasteItemsFromModel());

    Any ideas why I can’t access my model when the code is inside sap-ui-custom.js?

    Thanks

    Christian

    /
    • Hi Christian,

      how does the bootstrap of your component look like? Do you use the ComponentSupport? Or do you have an alternative bootstrap? Normally, if a Component is being loaded with the Component factory via manifest first option, the manifest.json is loaded first and the models which have been flagged as preload models are created during the Component load and are available afterwards in your view. In the self-contained build the timing could be slightly different when the modules are already loaded. But normally, the model should be also preloaded. How does the configuration for the waste_items model in the manifest.json looks like? Or do you create this model manually? If yes, where?

      Thanks and best regards,

      Peter

      • Hi Peter,

        thank you for getting back so quick.

        Do you use the ComponentSupport? Yes I do

        Here is my bootstrap (original, not the self-build one)

        <script id="sap-ui-bootstrap" 
        		src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
        		data-sap-ui-theme="sap_fiori_3_dark" 
        		data-sap-ui-resourceroots='{
        				"com.fidschenberger.wasteStatsApp": "./"
        		}' 
        		data-sap-ui-oninit="module:sap/ui/core/ComponentSupport" 
        		data-sap-ui-compatVersion="edge"
        		data-sap-ui-async="true" 
        		data-sap-ui-frameOptions="trusted"
        		data-sap-ui-logLevel="info">
        		</script>

        How does the configuration for the waste_items model in the manifest.json looks like?

        "models": {
        			"i18n": {
        				"type": "sap.ui.model.resource.ResourceModel",
        				"settings": {
        					"bundleName": "com.fidschenberger.wasteStatsApp.i18n.i18n"
        				}
        			},
        			"waste_types": {
        				"type": "sap.ui.model.json.JSONModel",
        				"uri": "model/wasteTypes.json"
        			},
        			"waste_items": {
        				"type": "sap.ui.model.json.JSONModel",
        				"uri": "model/wasteItemsEmpty.json"
        			},
        			"waste_statistics": {
        				"type": "sap.ui.model.json.JSONModel"
        			},
        			"configuration": {
        				"type": "sap.ui.model.json.JSONModel",
        				"uri": "model/config.json"
        			}
        		},

        I can try to set the preload flag and see if this fixes the problem.

        Christian

        • Hi Christian,

          thx for the details but I think I mixed it up in my brain. The preload flag for the models is just the time when the models are created. Preload means the models are created early during the load of the component. If the preload flag is not present, the models will be also created but at a later point in time once the Component instance is being created. There must be something wrong. Maybe a wrong manifest is being loaded for the Component.

          BR, Peter

          • Hi Peter,

            well I tried it anyways and at least now my table is getting rendered with the model data.

            But you are right. There is still something wrong. When I access the model in AfterRendering it is still empty. So my chart is not getting rendered.

            I will have a look again. Maybe I spot the error.

            In case I don’t spot any error, I would make my github repo public and maybe you could have a quick look. I know you are a busy guy who has more important things todo then fixing someones problems but the error might jump right into your face 🙂

            Cheers

            Christian

          • I have no ideas any more.
            I tried to be “smart” by accessing the models via the OwnerComponent but still the same result:

            getModel: function (sName) {
             return this.getOwnerComponent().getModel(sName);
            },

            Any more ideas from your side Peter?

             

          • Hi Christian,

            I sent you a private message in Twitter, sorry for the long delay. There seems to be a timing issue as you rely on the data of the models which has not been yet loaded at time you access it in the rendering phase. The models are properly created but the data you want to operate on is missing. There is a dataLoaded Promise on the JSONModel which can be use to wait until the data is fully loaded and you can access it in the callback. The good thing about the dataLoaded Promise is that it will resolve even if the data has been loaded already. So, there should be no timing issue anymore.

            BR, Peter

          • Well I forgot to post my solution.

            As I can only work on my private project when I have some time left, I completely forgot that I am doing things differentely compared to the sample app. DO’H

            As it is a PWA enabled app I use localForage for retrieving data etc.

            If anyone is interested check out the repo https://github.com/christianp86/Trasta

            I had to do some minor adjustments to my code to make the self-containment work. Chrome still gives me a ZERO for the performance 🙁

            Maybe I am able to improve that…working on it 🙂

            Thanks again Peter for taking the time.

  • Peter, thank you very much for this useful blog. Hopefully this will help us boost the startup performance for our SAPUI5 apps on the portal.

    As one of the guys mentioned above, dummy users are comparing the performance of SAPUI5 apps with  Angular and other frameworks. I should say, when comparing Angular app startup time with SAPUI5 app on the SAP portal, it is much faster. Agular app takes less than a second whereas a simple SAPUI5 app takes at least 3 seconds to start and that is causing us a problem to defend using the SAPUI5 framework.

    I hope with the progressive build this will improve that. One comments on that though, can I use this build for apps hosted on the gateway? I tried to do the progressive build only without doing the other builds before and looks like many libraries are missing.

    Thanks Again Peter,

    Add Belati