Content Navigation
Part 1: A simple UI5 app that runs in the SAP Launchpad Service
Part 2: Multiple Apps with a Shared Reuse Library
(Current page)
Part 3: Splitting bigger projects
Multiple Apps with a Shared Reuse Library
In part 1 you have learned how to build and deploy a single app. In this 2nd part this is now extended to multiple apps that share a reuse library with common code.
The library contains a control as well as a reuse component.
The sample code can be found in the
GitHub repository.
Deployed Content
Let’s have a look at how the deployment looks now and what has changed. The needed service instances haven't changed at all, but more HTML5 apps are now deployed to the same app host:
Both apps depend on the reuse library, which is expressed in their
manifest.json file under
sap.ui5/dependencies. The reuse library contains an embedded
manifest.json for the component.
MTA Project Structure
The
mta.yaml of the project has been enhanced slightly. Two more modules for the 2nd app and the library are added and they are linked to the UI deployer module:
The UI5 Library
The library only contains the bare minimum to demonstrate relevant aspects. Here it contains a sample reusable control and an embedded reusable component. Of course there could also be other reusable content like base classes, etc.
Build
The UI5 library is also built with
UI5 Tooling. The build is controlled the same way as the apps via the
package.json and the
ui5-deploy.yaml.
In the
ui5-deploy.yaml and the
manifest.json the type is now set to
library.
For libraries the builder requires a different project structure. Instead of a
webapp folder there must be a
src folder..
Since version 3.0 of the UI5 Tooling, it is no longer needed to add a folder structure for the library namespace.
Instead of a
Component.js file the library contains the files
library.js and
.library to define the library metadata.
The build produces a
library-preload.js file that packages all minified files including the sources of the reuse component. No dedicated
Component-preload.js is produced.
Routings
The
xs-app.json file only contains a route to serve its own sources.
This lib doesn't use an own OData service, but this would be possible for the embedded component.
In that case you would have to create a corresponding route here.
{
"authenticationMethod": "route",
"routes": [
{
"source": "^(.*)$",
"target": "$1",
"service": "html5-apps-repo-rt",
"authenticationType": "xsuaa"
}
]
}
The lib doesn't need to be started standalone and therefore has no
ui5.yaml with a local middleware.
Note that if you want to build unit test pages for the library you might need a local test environment for it as well
in the same way as for the apps.
App Descriptor
It has a
manifest.json file that defines a unique
sap.app/id and the
sap.app/type library for it. There are no inbounds and no services defined, but the
embeds property lists the subfolder of the embedded reuse component
"sap.app": {
"id": "btp.samples.multiple.apps.lib",
"type": "library",
"i18n": "i18n/i18n.properties",
"title": "{{title}}",
"applicationVersion": {
"version": "1.0.0"
},
"embeds":["comp/reuse"]
}
It also specifies
sap.cloud/service with the same service name as for the apps. This way it will also be bound to the single XSUAA instance.
"sap.cloud": {
"public": true,
"service": "btp.samples.multiple.apps"
}
The Embedded Component
The component is located in the
comp/reuse subfolder
. It is kept simple and only contains a Main view that renders a simple button in the
Main.view.xml file. It doesn't declare any models or use any OData services but this would be possible
in the same way as for an application.
The
manifest.json defines
type as
component and an ID which is constructed including the parent library ID and the relative path inside. This is needed to avoid any issues with module path resolution.
It declares the
embeddedBy property with the path to the embedding library. This is an important indicator for the platform that the preload package of the component is part of the library preload package. Without it, there might be an unnecessary request at startup which ends with a 404 Not Found error.
{
"_version": "1.32.0",
"sap.app": {
"id": "btp.samples.multiple.apps.lib.comp.reuse",
"type": "component",
"i18n": "i18n/i18n.properties",
"title": "{{appTitle}}",
"description": "{{appDescription}}",
"applicationVersion": {
"version": "1.0.0"
},
"embeddedBy": "../../"
}
}
There is no need to specify the
sap.cloud node here. This will be inherited from the
manifest.json of the lib.
The UI5 Apps
For the apps
(app1 and app2) there are only small changes.
In the
manifest.json of the first app the dependency to the library has been added.
"sap.ui5": {
"dependencies": {
"minUI5Version": "1.95.0",
"libs": {
"sap.ui.core": {},
"sap.m": {},
"sap.f": {},
"btp.samples.multiple.apps.lib": {}
}
}
}
There are no resourceRoots URLs defined here, because these depend on the way how the apps are started.
The
Master.view.xml uses the library by adding the Reuse control.
<mvc:View
controllerName="btp.samples.multiple.apps.app1.controller.Master"
xmlns="sap.m"
xmlns:semantic="sap.f.semantic"
xmlns:mvc="sap.ui.core.mvc"
xmlns:lib="btp.samples.multiple.apps.lib">
<!-- Reuse control from shared lib -->
<lib:controls.Reuse text="App1" />
</mvc:View>
In the
manifest.json of the 2nd app there is also a
component usage for the reuse component.
"sap.ui5": {
"dependencies": {
"minUI5Version": "1.95.0",
"libs": {
"sap.ui.core": {},
"sap.m": {},
"sap.f": {},
"btp.samples.multiple.apps.lib": {}
}
},
"componentUsages": {
"resuse": {
"name": "btp.samples.multiple.apps.lib.comp.reuse",
"settings": {},
"componentData": {},
"lazy": false
}
}
}
In the
Master.view.xml there is a component container that shows the component usage.
<mvc:View
controllerName="btp.samples.multiple.apps.app2.controller.Master"
xmlns="sap.m"
xmlns:semantic="sap.f.semantic"
xmlns:core="sap.ui.core"
xmlns:mvc="sap.ui.core.mvc"
xmlns:lib="btp.samples.multiple.apps.lib">
<semantic:titleContent>
<Image id="titleImage" />
<!-- Reuse control from shared lib -->
<lib:controls.Reuse text="App2" />
<core:ComponentContainer usage="resuse" manifest="true" async="true" />
</semantic:titleContent>
</mvc:View>
Routings
The
xs-app.json files of the apps haven't changed. There is no route for the library added because the library will be accessed directly and not via the apps.
However the
ui5.yaml files that define the server for the local run have been enhanced.
They define the entire isolated test environment for an app and therefore also need a route mapping for the static sources of the lib.
server:
customMiddleware:
- name: fiori-tools-servestatic
afterMiddleware: compression
configuration:
paths:
- path: /btpsamplesmultipleapps.btpsamplesmultipleappslib
src: "../lib/src"
Again to start the local test just run the
start or
start-local script in one of the
package.json files.
Running the Deployed Apps
Deploying the App
Build and deploy the single MTA project in the same way as before.
Running Standalone
After the deployment the apps can be run standalone from their
index.html via the
managed approuter. Again you can find them in the SAP BTP cockpit on the HTML5 Applications page under your subaccount.
The
index.html need to define a resource mapping for the library to tell UI5 where to find the resources. Unfortunately this can't be hardcoded here, because of the destination guid which is a part of the URL. The guid and the service name are the same as for the index.html but the library ID is at the end.
Since there is no way to specify this as a relative URL, there is a small piece of code to calculate it:
<script id="sap-ui-bootstrap"
src="https://ui5.sap.com/resources/sap-ui-cachebuster/sap-ui-core.js"
data-sap-ui-theme="sap_fiori_3"
data-sap-ui-resourceroots='{
"btp.samples.multiple.apps.app1": "./"
}'
data-sap-ui-libraries="sap.m"
data-sap-ui-oninit="module:sap/ui/core/ComponentSupport"
data-sap-ui-compatVersion="edge"
data-sap-ui-async="true"
data-sap-ui-preload="async"
data-sap-ui-frameOptions="trusted">
</script>
<script>
let parts = window.location.pathname.split('/');
let destinationGuid = (parts.length > 2) ? parts[1].split(".")[0] + "." : "";
sap.ui.loader.config({paths:{"btp/samples/multiple/apps/lib": `/${destinationGuid}btpsamplesmultipleapps.btpsamplesmultipleappslib`}});
</script>
The script also contains a condition to support the local run. Here the paths are also different, and a different relative path is needed.
Running in the Launchpad Service
To run the apps in the launchpad, synchronize the html5 provider in the
launchpad configuration and configure the apps. Afterwards you can run the new apps in a similar way as before.
Here the platform will provide the URLs to access the apps and the library.
If you check the
ui5AppInfo.json that is used to start the app you can find the additional dependencies for the library. The reuse component doesn't appear here, because it is embedded in the library and all paths can be derived from it.
{
"name": "btp.samples.multiple.apps.app2",
"url": "/b7f4982c-02d4-4c24-a4f6-5f23ff2f8aef.btpsamplesmultipleapps.btpsamplesmultipleappsapp2/~060222132517+0000~/",
"manifest": true,
"asyncHints": {
"libs": [
{
"name": "sap.m",
"lazy": false
},
{
"name": "btp.samples.multiple.apps.lib",
"url": {
"url": "/b7f4982c-02d4-4c24-a4f6-5f23ff2f8aef.btpsamplesmultipleapps.btpsamplesmultipleappslib/~060222132517+0000~/",
"final": true
},
"lazy": false
},
{
"name": "sap.f",
"lazy": false
},
{
"name": "sap.ui.fl",
"lazy": false
}
],
"components": []
},
"messages": [],
"version": "1.0.0"
}
Next Steps
Now you are ready to build multiple apps and move common parts of them into a reusable library.
However, everything is still contained in the same MTA project. In bigger projects, you might need to split this into smaller parts in order to achieve independent lifecycles and split responsibilities.
Please check out
Part 3 of this blog for this.