Consume UI5 Library in Local Development via Local Approuter

A while ago I had to add and consume a Custom UI5 Library in my development project. When checking out the possibilities on how to achieve this, I came across a very well explained blog post on How to consume UI5 custom libraries in BAS or VS Code via Yarn workspaces by Mio Yasutake . I tested out the solution in the blog post mentioned above and it seemed to work out perfectly.

Mio’s blog post combined with Wouter Lemaire his post on Using UI5 Libraries in CF approuter is the perfect toolkit to get started on this topic in this specific scenario.

If I’m not mistaken you can also configure the access to your library inside the Fiori project its ui5.yaml file, which is a handy feature but was not most ideal in my case.

At the point that I had to develop and integrate the Custom UI5 Library, my current project setup was already in place. Which made me wonder whether I could achieve the same result in a different way. Curious about this “current project setup” and “different way”? Then let us continue below! 😊

Current Project Setup

In most of my development projects I use a Standalone Approuter (usually named LocalLaunchpad) with an “flpSandbox.html” file as entry point. In this file I add all the Fiori Applications which I want to make available on my Fiori Launchpad Sandbox.

The approuter (LocalLaunchpad) directory structure, inside the RootDirectory:

RootDirectory ├ LocalLaunchpad ├ webapp ├ flpSandbox.html ├ manifest.json ├ default-env.json ├ package.json └ xs-app.json

The content of the flpSandbox.html looks as such:

<!DOCTYPE html>
<html> <head> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Fiori Launchpad Sandbox</title> <script> window["sap-ushell-config"] = { defaultRenderer: "fiori2", bootstrapPlugins: { "KeyUserPlugin": { "component": "sap.ushell.plugins.rta" }, "PersonalizePlugin": { "component": "sap.ushell.plugins.rta-personalize" } }, applications: { "action-display": { title: "My App", description: "Uses Custom Lib", additionalInformation: "SAPUI5.Component=my.demo.test.lib.MyApp", applicationType: "URL", url: "/ui/MyApp/uimodule/webapp", navigationMode: "embedded" } } }; </script> <script id="sap-ui-bootstrap" src="/test-resources/sap/ushell/bootstrap/sandbox.js"></script> <script id="sap-ui-bootstrap" src="/resources/sap-ui-core.js" data-sap-ui-resourceroots='{"LocalLaunchpad": "./"}' data-sap-ui-theme="sap_fiori_3" data-sap-ui-compatVersion="edge" data-sap-ui-async="true" data-sap-ui-frameOptions="allow"></script> <script> sap.ui.getCore().attachInit(() => sap.ushell.Container.createRenderer().placeAt("content")); </script> </head> <body class="sapUiBody" id="content"></body>
</html>

You can see that one application has been added, along with its configuration properties:

"action-display": { title: "My App", description: "Uses Custom Lib", additionalInformation: "SAPUI5.Component=my.demo.test.lib.MyApp", applicationType: "URL", url: "/ui/MyApp/uimodule/webapp", navigationMode: "embedded"
}

The configuration properties and values above explained:

action-display semanticObject – action
title tile title
description tile description
additionalInformation SAPUI5 component – namespace.appname
applicationType asccessible via URL
url path to application (via approuter route in xs-app.json)
navigationMode embedded inside FLP page

The content of the manifest.json looks as such:

{ "_version": "1.21.0", "sap.app": { "id": "LocalLaunchpad", "type": "application", "applicationVersion": { "version": "1.0.0" } }
}

The content of the package.json looks as such:

{ "name": "approuter", "engines": { "node": "^10.0.0 || ^12.0.0" }, "dependencies": { "@sap/approuter": "10.5.1" }, "scripts": { "start": "node node_modules/@sap/approuter/approuter.js" }
}

The content of the default-env.json looks as such:

{ "destinations": [ { "name": "ui5", "url": "https://ui5.sap.com" } ]
}

The content of the xs-app.json looks as such:

{ "welcomeFile": "/webapp/flpSandbox.html", "authenticationMethod": "none", "routes": [ { "source": "^/webapp/(.*)$", "target": "$1", "localDir": "./webapp", "cacheControl": "no-cache" }, { "source": "^/ui/(.*)$", "target": "$1", "localDir": "../", "cacheControl": "no-cache" }, { "source": "^/resources/(.*)$", "target": "/resources/$1", "authenticationType": "none", "destination": "ui5" }, { "source": "^/test-resources/(.*)$", "target": "/test-resources/$1", "authenticationType": "none", "destination": "ui5" } ]
}

Note that the “welcomeFile” property is pointing to the “flpSandbox.html” page which contains the tile to the application. This “flpSandbox.html” can be reached because of the configured webapp route.

Running the LocalLaunchpad Approuter with the configuration above:

Obviously the tile will not open the application yet, since it does not exist.

Add a SAPUI5 Fiori Application

Generate a SAPUI5 Fiori application inside your RootDirectory (using the Easy UI5 Generator for example). Use the namespace: my.demo.test.lib and application name: MyApp for your application.

The directory structure, inside the RootDirectory so far:

RootDirectory ├ LocalLaunchpad ├ MyApp ├ uimodule ├ webapp ├ … └ manifest.json

Add the following “crossNavigation” to your manifest.json file:

"crossNavigation": { "inbounds": { "intent1": { "signature": { "parameters": {}, "additionalParameters": "allowed" }, "semanticObject": "action", "action": "display", "title": "My App", "icon": "sap-icon://competitor" } }
}

When opening the application via the tile at this point:

Add a SAPUI5 Custom Fiori Library

Generate a SAPUI5 Custom Fiori Library inside your RootDirectory. I used the Easy UI5 Generator in this example. Adding this library will result in the following project structure:

RootDirectory ├ LocalLaunchpad ├ MyApp └ CustomLib

You can start the library by running the “npm start” command in a separate terminal window. The library will be served at your localhost port 8080.

Consume the Custom Fiori Library

To consume the library inside your Fiori Application, add the following configuration to your app:

1.  Add the library under your “dependencies” its “libs” configuration:

"dependencies": { "libs": { ... , " my.demo.test.lib.CustomLib": {} }
}

2.  Load the library inside your component at the very top of the Component.js file:

sap.ui.getCore().loadLibrary("my.demo.test.lib.CustomLib", "/mydemotestlibCustomLib");

⚠️ If you prepare the app for a managed approuter scenario when deployed, you can add the “sap cloud service” or Business Solution name as well: ⚠️

sap.ui.getCore().loadLibrary("my.demo.test.lib.CustomLib", "/{sap.cloud.service.name}.mydemotestlibCustomLib");

3.  Consume a custom control from your library in the view.xml file:

Add an XML namespace inside your view file:

xmlns:myLib="my.demo.test.lib.CustomLib"

Add the custom control:

<myLib:Example text="UI5 library via Approuter locally"/>

There is no use in trying to open the app at this point, since the approuter/flpSandbox has no clue where to find the Custom Fiori Library. To enlighten him let’s add the required configuration!

Complete the Approuter

To finalize the approuter/flpSandbox add the following route to the xs-app.json file:

{ "source": "^/mydemotestlibCustomLib/(.*)$", "target": "/resources/my/demo/test/lib/CustomLib/$1", "authenticationType": "none", "destination": "customfiorilibrary"
}

⚠️ In case a managed approuter preparation was set in the previous step, use the following configuration for your route instead: ⚠️

{ "source": "^/{sap.cloud.service.name}.mydemotestlibCustomLib/(.*)$", "target": "/resources/my/demo/test/lib/CustomLib/$1", "authenticationType": "none", "destination": "customfiorilibrary"
}

Add the following destination in the default-env.json file:

{ "name": "customfiorilibrary", "url": "http://localhost:8080"
}

Adding the destination above ensures that your route in the xs-app.json can find the Custom Fiori Library.

Restart your approuter and make sure your Custom Fiori Library is still running in its own terminal window.

Magic is served 🧙‍♂️

The Custom Fiori Library its custom control is consumed in the Application via the approuter.

If you prefer to serve both the approuter and Custom Fiori Library inside the same terminal, you can install the “npm-run-all” npm package as a dev dependency:

npm i npm-run-all --save-dev

Adjust the approuter its start script as such:

"scripts": { "start": "npm-run-all -p start:approuter start:customFioriLibrary", "start:approuter": "node node_modules/@sap/approuter/approuter.js", "start:customFioriLibrary": "npm start --prefix ../CustomLib"
}

Next time you run your approuter, both the approuter and Custom Fiori Library will be served parallel inside the same terminal, thanks to the npm-run-all package.

Conclusion

Thanks to Mio and Wouter their post I got a better understanding on how this setup and consumption of Custom Fiori Libraries work. It helped me to figure out a way to consume such a library inside my current project setup, as explained above. Additional configuration is/might be required for deployment and can differ depending on the use case.

I hope you found it interesting and that it might be useful to you in further demoes or projects.

Best regards,

Dries