Skip to Content
Technical Articles

SAPUI5 custom libraries: Deploy to ABAP Repository and use in applications

Introduction

This blog could be interesting for those who struggled in deploying and using custom SAPUI5 libraries from the ABAP Repository. I simply want to share my findings, because I could not find official documentation about this. Instead, I read a lot of blog posts addressing the custom library topic, but all of them suggests different ways to get it working, where some of them are hard coded solutions which I wanted to avoid. After struggling several days in creating custom libraries and reusing them in applications, I finally found some interesting things and a generic way to reference them. At the end I noticed, that I always had a wrong assumption on how things are working. Especially the difference between the WebIDE, the ABAP repository, the HCP and the Fiori Launchpad confused me a lot, because things work differently.

Goals and Requirements

The main goal was to achieve the following scenario:

  • create a library (componet-id: my.custom.lib) and deploy it to ABAP repository
  • create an application (component-id: my.custom.app) and deploy it to ABAP repository
  • consume the library my.custom.lib in the application my.custom.app
  • the library should be automatically loaded from the place in the ABAP repository where it was deployed, so it should not be necessary to use any hard coded path mappings
  • it should work as a standalone app (without Fiori Launchpad) and also within the Fiori Launchpad (I first concentrated on getting it working without the Fiori Launchpad, but it turned out that I should have started with the latter one)

When testing this scenario by deploying everything the HCP, it works by configuring the neo-app.json correctly. There were no issue detected. I mainly struggled with the ABAP Repository and this blog mainly addresses the issues I detected there. I would recommend to check this blog from Sergei if you are interested in HCP deployment: https://blogs.sap.com/2016/12/15/sapui5-custom-control-library-web-ide-development-deployment-to-hcp-and-to-on-premise-abap-repository.-part-1./

I used to following tools and versions (maybe it also works for older versions, but here I don’t have much experience):

  • SAP WebIDE
  • SAPUI5 1.52
  • SAP 7.50

Creating Application and Library

Before you continue, you should read the following blog post from Nabi, because it is necessary to understand that the App-Index must work properly in your SAP system to get it working:

https://blogs.sap.com/2017/11/18/the-ui5-app-index-a-demo-using-sap-web-ide-full-stack-to-clone-a-github-repo-build-via-grunt/

With the App-Index, SAP knows …

  • which components and libraries are deployed,
  • which dependencies they have and
  • where to find each component and library in the system (the path to the ICF service)

 

First of all, I created two projects in the WebIDE, one for the app and one for the library. The projects are based on the templates provided by SAP and I recommend to use them. The application is based on the template “SAPUI5 Application” and the library is based on the template “SAP Fiori Library”. Both come with Grunt build which ensures that the deployed app and library have the correct files and structure. The screenshots below shows which templates I used in the WebIDE.

 

The next step we have to do is to reference the library in our application. Therefore, it should be enough to add an entry in the libs sections of the “manifest.json” file of the application. It should not be necessary to enter any hard coded “resourceroots” paths or something else in the index.html.

"sap.ui5": {
	...
	"dependencies": {
		"minUI5Version": "1.30.0",
		"libs": {
			"sap.ui.layout": {},
			"sap.ui.core": {},
			"sap.m": {},
			"my.custom.lib": {}
		}
	},
	...
}

 

Deployment

After that, you can deploy both the library and the application to the ABAP repository by using the WebIDE. The application and the library are both deployed as BSP applications. It doesn’t matter how the library is named and where it is located in the ABAP Repository – the real path to it should be later figured out via the component-id (in our case “my.custom.lib”). Deployment can be done in WebIDE by rightclicking on the project and select “Deploy > Deploy to SAPUI5 ABAP Repository”. Follow the steps in the wizard dialog.

In my case, the application was deployed as “zmycustomapp” to https://abap.mycompany.com/ sap/bc/ui5_ui5/sap/zmycustomapp and the library was deployed as “zmycustomlib” to https://abap.mycompany.com/ sap/bc/ui5_ui5/sap/zmycustomlib

After deployment was successful, you should check the App-Index of the library and the application as Nabi suggested by using the “ui5_app_info” service (for details see Nabis post mentioned above):

  • https://abap.mycompany.com/sap/bc/ui2/app_index/ui5_app_info?id=my.custom.lib
  • https://abap.mycompany.com/sap/bc/ui2/app_index/ui5_app_info?id=my.custom.app

Please verify that the result for the application should mention the library as one of its dependencies! And the property “url” should mention the path to the library, in my case it is “sap/bc/ui5_ui5/sap/zmycustomlib”. If everything is successful, we can start testing the application.

 

Testing the Application

The first thing I did was checking if it worked without the Fiori Launchpad. I simply called the ICF service and requested the index.html:

https://abap.mycompany.com/sap/bc/ui5_ui5/sap/zmycustomapp/index.html

You should notice that the library is not loaded, but all SAP libraries like “sap.m” etc. are loaded successfully. In the debugger you should see a request which tries to load the library “my.custom.lib”:

https://abap.mycompany.com/sap/bc/ui5_ui5/sap/zmycustomapp/resources/my/custom/lib/library.js

And in the console you should see that the loading failed:

sap-ui-core-dbg.js:18684 Uncaught Error: failed to load my/custom/lib/library.js' from 
./resources/my/custom/lib/library.js: 404 - NOT FOUNDat requireModule (sap-ui-core-dbg.js:18684)at 
Object.jQuery.sap.require (sap-ui-core-dbg.js:19265)at constructor.Core.loadLibrary (Core-dbg.js?
eval:1715)at Interface.eval [as loadLibrary] (Interface-dbg.js?eval:44)at constructor.loadDependencies 
(Manifest-dbg.js?eval:446)at constructor.init (Manifest-dbg.js?eval:543)at 
UIComponentMetadata.ComponentMetadata.init (ComponentMetadata-dbg.js?eval:170)at 
fnClass.Component._initCompositeSupport (Component-dbg.js?eval:564)at eval (ManagedObject-dbg.js?
eval:492)at fnClass.constructor (ManagedObject-dbg.js?eval:516)

 

That was the point where I was quite confused, because all the SAP libraries are loaded successfully which meant that it was somehow possible to resolve their “…/resource/sap/m/library.js” requests. I thought that it should also resolve the requests from my custom library, but it was not the case. But the App-Index in my case was correct as Nabi explained.

After reading Sergeis blog (https://blogs.sap.com/2016/12/20/sapui5-custom-control-library-web-ide-development-deployment-to-hcp-and-to-on-premise-abap-repository.-part-2./) we know that SAP stores their libraries in the MIME repository, unlike our own custom libraries which are stored as BSP application somewhere else. My assumption was that resolving the libraries must exist on the backend, and indeed that is the case, but unfortunately only for the SAP libraries in the MIME repository. The ICF service node “/sap/bc/ui5_ui5” defines http handlers, and one of them (I think it was /UI5/CL_UI5_HTTP_HANDLER2) contains the logic of resolving the library paths like “…/resource/sap/m/library.js” by looking up the content of the MIME repository (during debugging with my colleague we saw that there is a table where all SAP libraries are listed, but not our own ones).

 

Final Conclusion

So the question is how should that ever work?? As Nabi said, it should work, but it does not in my case. Therefore, as a next step, I checked if it works if I register the app in the Fiori Launchpad of the SAP system. And here I could verify that everything works. I was very surprised and started to take a look what is different here.

After debugging the launchpad, I could see that SAP does a request to

https://abap.mycompany.com/sap/bc/ui2/start_up?so=…&action=…&…

 

This request returns a json with information about the application component and its dependencies – the structure is similar to the one you get from the “https://abap.mycompany.com/sap/bc/ui2/app_index/ui5_app_info“ call as mentioned before. With that information, the launchpad establishes the mapping from our library to the real path on the backend. As a conclusion, the CLIENT does the mapping for your custom libraries, not the backend! I’m not sure why it was designed in that way, because the backend system already knows everything and the http handler which resolves the “/resource/….” requests could also resolve the ones for our custom libraries. That means, when loading the application component in the launchpad, the module paths for the depending libraries are registered automatically by using the method “jQuery.sap.registerModulePath” and the information from the “/sap/bc/ui2/start_up” request (see https://sapui5.hana.ondemand.com/1.52.28/#/api/jQuery.sap/methods/jQuery.sap.registerModulePath). The client simply maps the library „my.custom.lib“ to the path on the backend, which is in my case „/sap/bc/ui5_ui5/sap/zmycustomlib“.

 

Now I knew why it does not work when testing my application without the launchpad by just using just the „index.html“. In order to fix this, we have to do the similar thing as the launchpad does: We have to first request the app-index on the client for our application component and register the module paths accordingly. That must be the first thing you do in the js script of the index.html. The path registration must happen before you attach the callback which finally bootstraps the shell with our component. I wrote a small script which does this in a generic way. The script is put next to the index.html file and is called “library-setup.js” (see content below). The script exposes a method on the “sap” namespace (I know it is regarded as bad practice to extend the namespace of someone else, but in that case I did an exception 😊). The method expects the component id of your app and internally registers the module paths of its dependencies asynchronously. Fortunately, the ui5_app_info also contains all transitive dependencies, meaning the dependencies of its own dependencies. Therefore only one backend call is needed. Please note that the dependencies from SAP do not have an “url” defined, so we can skip them. If everything was successful, the returned promise is resolved:

(function(sap) {
    
    /**
     * Registers the module paths for dependencies of the given component. 
     * It fetches the app-index of the component, checks its dependencies
     * and finally registers the module paths via "jQuery.sap.registerModulePath"
     * if an explicit url is defined for a dependency. Make sure the ICF service
     * "/sap/bc/ui2/app_index" is active in your SAP system. 
     * 
     * @param {string} componentId The id of the component, e.g. "my.custom.app"
     * for which the dependencies should be registered.
     * @returns {Promise} A promise which is resolved when the ajax request for 
     * the app-index was successful and the module paths were registered. In case
     * the app-index could not be fetched for the given component, the promise is
     * rejected.
     */ 
    sap.registerComponentDependencyPaths = function(componentId) {
        var url = "/sap/bc/ui2/app_index/ui5_app_info?id=" + componentId;
        
        return new Promise(function(resolve, reject) {
            $.ajax(url)
    	        .done(function(data) {
    	            if(data && data[componentId]) {
    	                var moduleDefinition = data[componentId];
    	                moduleDefinition.dependencies.forEach(function(dependency) {
        	                if(dependency.url) {
        	                    jQuery.sap.registerModulePath(dependency.componentId, dependency.url);
        	                }
    	                });   
    	            } else {
    	                reject(new Error("No app info found for component '" + componentId + "'."));
    	            }
    	            
    	            resolve();
    	        })
    	        .fail(function(error) {
    	            reject(new Error("Could not fetch app info for component '" + componentId + "'. " + 
    	                "No module paths were registered for dependent libraries."));
    	        });  
        });
    };
    
})(sap);

 

Now, in your “index.html”, you should include this script file and call the method like shown below. Please note that I always bootstrap the shell even if the request failed so that it also works when the application is published to HCP and used there without the launchpad. You can adapt this for your needs.

 

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
	    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<title>customapp</title>
		<script id="sap-ui-bootstrap"
			src="./resources/sap-ui-core-dbg.js"
			data-sap-ui-theme="sap_belize"
			data-sap-ui-resourceroots='{"my.custom.app": "./"}'
			data-sap-ui-compatVersion="edge"
			data-sap-ui-preload="async"
			data-sap-ui-libs="sap.m">
		</script>
		<script src="./library-setup.js"></script>
		<link rel="stylesheet" type="text/css" href="css/style.css">
		<script>
		    sap.registerComponentDependencyPaths("my.custom.app")
    		    .catch(function(error) {
		            console.error(error);
		        })
		        .finally(function() {
		            sap.ui.getCore().attachInit(function() {
        				new sap.m.Shell({
        					app: new sap.ui.core.ComponentContainer({
        						height : "100%",
        						name : "my.custom.app"
        					})
        				}).placeAt("content");
    		        });  
		        });
		</script>
	</head>
	<body class="sapUiBody" id="content">
	</body>
</html>

 

Summary

If you only use the application and libraries in the Fiori Launchpad, it should be enough to follow Nabis explanations about the App-Index. If you want to use the application without the Fiori Launchpad, you have to somehow simulate the steps the launchpad does internally by registering the module paths for application dependencies. I hope this blog helped those who struggled in the same way like me. If you see any issues or if you have other solutions, please let me know, it would be great if there is a way to get rid of the “solution” I described above. Please note that this is just an idea how to overcome the hard coded path mappings for custom libraries. I hope there is a way to configure this completely on the backend, or if the HTTP handler on the backend resolves the resource requests for custom libraries one day in the future.

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