Skip to Content

Progressive Web Apps have opened us a lot of new ways to deliver amazing user experiences on the web. They combine the best of web and the best of apps, regardless of any operating system and browser choice. Once installed they feel and look like native applications without requiring special developing skills. Modern business applications should work on desktop and mobile systems, which is no issue thanks to the responsiveness of PWAs. To top it all: It works completely offline.

SAPUI5 gives us the opportunity to build fast, reliable, and responsive user interfaces for the web. There was already an article about Building a PWA with OpenUI5, but how about converting an existing OpenUI5 Application into a Progressive Web App?

Example: Icon Explorer

The icon explorer is a small tool that allows you to find and preview icons by browsing through categories and tags or just by searching. It loads the SAP icons font, gives an overview of the icons, and shows them in different use cases with UI5 controls. It is a great base for testing the PWA compatibility of UI5.

Live Demo

What do we need?

A default PWA needs:

  • Special meta tags in index.html to tell the browser that this is a web app and to load some icons and to make other modifications.
  • Splash screen and icons to beautify the user interface because Progressive Web Apps need some static content that is shown immediately
  • A Web App Manifest to describe our application. UI5 already requires a manifest, meaning that we can merge them by adding PWA-related terms in the existing manifest.json file
  • A service worker to guarantee offline compatibility

For more details, you can look up the PWA Checklist

After having made all these changes one big problem appears:

We have a lot of synchronous XMLHttpRequests but the service worker is not able to see any of them. To check this just open the Web Application with Chrome, start Developer tools, click on Network to see which files are loaded by the service worker: When you first start the page, the service worker will be registered and installed. When you now reload it, the service worker will start serving the files.

This gives us an overview of all files requested by the icon explorer. You will see that there are already a lot of files loaded by the service worker, but there are still various files not coming from there.

So, what else do we need?

1. Component-Preload

A component preload will combine all needed components in one file. This will help us to get fewer XHRs and to improve the start-up performance for delivering a better user experience. To create a Component-Preload.js file you can either use the SAPUI5 Web IDE or you can do it with Grunt. In this case, we will use Grunt.

We need to create a Gruntfile.js:

module.exports = function(grunt) {
      grunt.initConfig({
        dir:{
            webapp: 'IconExplorer/src',
            dist: 'dist'
        },

        clean: {
            "preload": ["src/Component-preload.js"],
            "openui5": ['src/openui5']
        },
        
        "openui5_preload": {
            component: {
                options: {
                    compress: false,
                    resources: {
                        cwd: "src",
                        prefix: "sap/ui/demo/iconexplorer",
                        src: [
                            "Component.js",
                            "**/*.js",
                            "**/*.fragment.xml",
                            "**/*.view.xml",
                            "**/*.properties",
                            "manifest.json",
                            "!Component-preload.js",
                            "!test/**",
                            "!openui5/**"
                        ]
                    },
                    dest: "src"
                },
                components: "sap/ui/demo/iconexplorer"
            }
        }
    });
    
    grunt.loadNpmTasks("grunt-contrib-clean");
    grunt.loadNpmTasks("grunt-openui5");
    
    grunt.registerTask('build', ['clean', 'openui5_preload']);
    grunt.registerTask('default', ['build']);
};

The openui5_preload task will generate our Component-preload.js file which contains our Component.jsmanifest.json and all other .js, .xmland .properties files.

2. Load components asynchronously

We created the Component-preload.js file to reduce the XMLHttpRequests, but the components are still loaded synchronously. Luckily UI5 offers us several opportunities to make things asynchronous.

To fix this, we need to modify the index.html file:

<script>
        sap.ui.getCore().attachInit(function () {
            sap.ui.require([
                "sap/m/Shell",
                "sap/ui/core/ComponentContainer"
            ], function (Shell, ComponentContainer) {
                var oCompContainer = new ComponentContainer({
                    height: "100%"
                });

                // initialize the UI component with async property
                var oComponent = sap.ui.component({
                    async: true,
                    manifestFirst: true,
                    name: "sap.ui.demo.iconexplorer"
                }).then(function(oComponent){
                    oCompContainer.setComponent(oComponent);
                });

                new Shell({
                    showLogout:false,
                    app: oCompContainer
                }).placeAt("content");
                });
        });
</script>

3. Load libraries asynchronously

After having all components loaded asynchronously, we also need to do this with the libraries: Just add this to the bootstrapping part of index.html:

data-sap-ui-preload="async"

This way the libraries are loaded asynchronously, but you have to check, if there are still synchronous requests for libraries. In our case, there are synchronous requests for Avatar.js and library.js file from sap.f-library. This means we must preload the sap.f library.

4. Preload libraries

To preload libraries, we just have to add them to data-sap-ui-libs in bootstrap of index.html:

data-sap-ui-libs="sap.m, sap.ui.layout, sap.ui.core, sap.f"

Now, there are two library requests left: The library-parameters.json files. They are requested, because the browser tries to render the UI before the theme is fully loaded. To solve this, we have to tell the browser in bootstrapping of UI5 in index.html, that it has to wait for the theme:

data-sap-ui-xx-waitForTheme="true"

5. Messagebundles preload

(We do not need this step, when we use the OpenUI5 Nightlies, because after version 1.52 of OpenUI5 the messagebundles are loaded asynchronously. But for older versions we need to preload these files to prevent requests.)

After fixing this, we only have six XHRs. Three of them are asynchronous, so there are just three synchronous requests left:

These three files are the messagebundle files. They are for standard texts in Components, for example the “Search” text of a Search field.

To make them load asynchronously, there is unfortunately no property or tag. We need a workaround:

Firstly, we have to download packaged versions of our libraries with Bower to reach the messagebundle.properties files, by creating this bower.json file:

{
    "name": "iconexplorer",
    "dependencies": {
      "openui5-sap.ui.core": "openui5/packaged-sap.ui.core",
      "openui5-sap.m": "openui5/packaged-sap.m",
      "openui5-sap.f": "openui5/packaged-sap.f",
      "openui5-sap.ui.layout": "openui5/packaged-sap.ui.layout"
    }
}

Secondly, we need to run this command in terminal

$ bower install

Bower will now save our libraries in bower_components directory.

Thirdly, we must must extend our existing Gruntfile.js with one more task called concat:

concat: {
	"sap-ui-messagebundle-preload.js": {
		options: {
			process: function(src, filepath) {
				var moduleName = filepath.substr(filepath.indexOf("resources/") + "resources/".length);
				var preloadName = moduleName.replace(/\//g, ".").replace(/\.js$/, "-preload");
				var preload = {
					"version": "2.0",
					"name": preloadName,
					"modules": {}
				};
				preload.modules[moduleName] = src;
				return "jQuery.sap.registerPreloadedModules(" + JSON.stringify(preload) + ");";
			}
		},
		src: [
			"bower_components/openui5-sap.ui.core/resources/sap/ui/core/*.properties",
                        "bower_components/openui5-sap.m/resources/sap/m/*.properties",
                        "bower_components/openui5-sap.ui.layout/resources/sap/ui/layout/*.properties",
                        "bower_components/openui5-sap.f/resources/sap/f/*.properties"
		],
		dest: "src/openui5/resources/sap-ui-messagebundle-preload.js"
	}
}

This task will compress all messagebundle files in one JavaScript file and register each file as preloaded, so it does not need to be requested.

The generated sap-ui-messagebundle-preload.js file by Grunt needs to be called in index.html to be recognized:

<!-- Preload Messagebundle files -->
<script src="openui5/resources/sap-ui-messagebundle-preload.js"></script>

At last, we also have to add this to bootstrapping of UI5 in index.html:

data-sap-ui-xx-supportedLanguages="default"

If we do not define a language for our application (e.g with sap-ui-language), UI5 tries to load the messagebundle file according to the browser language. This can lead to requests for very specific language files like de_DE, but UI5 is “only” translated into ~38 languages with one version of German (*de*), causing a language loading fallback. Our preload also contains only these languages, so we have to tell UI5, that it has to load just supported languages. Setting the supported languages to default will avoid any request for languages not existing in UI5.

Test

Finally, our application should now load everything from the service worker. This means that our Progressive Web App is finished and works completely offline. Since Chrome 59, a PWA will also appear in the App Drawer on Android devices, when added to the homescreen, delivering an even more native-like user experience.

Conclusion

This example showed us that Progressive Web Apps are indeed no limitation for OpenUI5. In summary we can say, that it is enough to create a Gruntfile.js, which preloads all components and libraries, to convert our existing OpenUI5 application into a Progressive Web App. But we also have to point out that the icon explorer is a standalone application with no synchronization. For the future we should also consider to examine how OpenUI5 is performing as PWA when it comes to synchronization and data exchange.

Mehmet is a student of Business Information Technology at SAP who is very interested in discovering new technologies and a passionate programmer. Especially mobile and frontend development gives him great pleasure.

 

To report this post you need to login first.

1 Comment

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

  1. Frank Luzat

    Good one! I’m especially interested in reading “how OpenUI5 is performing as PWA when it comes to synchronization and data exchange.”

     

    I would appreciate a follow up on that topic here!

    (0) 

Leave a Reply