Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
The UI5 Tooling and its ecosystem together are the key pillars of a modern development experience for UI5 applications and libraries. With the UI5 Tooling it is easily possible to create, develop, test and build UI5 projects with a Node.js based tooling.
Let’s first take a look back into the evolution of the UI5 Tooling and its important milestones over the last couple of month and years to get to a modern, open and extensible development experience:
A very important milestone of the UI5 Tooling evolution was extensibility. Being now open and extensible allows everyone to create custom tasks and custom middlewares for their special needs and it also avoids the UI5 Tooling team to be the bottleneck to implement new features. This was the starting signal of the UI5 Tooling ecosystem. The ecosystem is the foundation and provided the examples how custom extensions can be developed and shared.
Another big milestone was UI5 Tooling V2. This version introduced a custom package manager which can handle the UI5 frontend packages properly. The node package manager doesn’t do negotiation of dependencies which can lead to inconsistencies. But for frontend packages you need to ensure to have only one version per dependency available and even more the dependencies need to be in sync with each other as we want to depend on a specific UI5 version which consists of several UI libraries available as individual npm packages. The UI5 version defines in which version the UI libraries are included. Together with the frontend package manager, all OpenUI5 and SAPUI5 UI libraries have been published to the public npm registry for general availability.
Finally, the TypeScript enablement of OpenUI5 and SAPUI5 enables us having a modern development experience for UI5 development. You can now use ES modules and ES classes to write your UI5 applications. Additionally, modern IDEs will provide you a lot of support out of the box, e.g., code completion, type checks and more. You can check it out by yourself by following the blog post “Getting Started with TypeScript for UI5 Application Development”. There you will get some more background information and can start to make your fingers dirty with TypeScript development for UI5.
But there was always one aspect missing for me so far: I want to directly consume node packages directly in my UI5 application similar like it is possible for other UI frameworks. There is already a solution for UI5 applications using the UI5 Tooling to use project shims. Vivek Chandregowda explained this is a nice blog roughly a year back: “Project Shims – Using NPM modules with UI5”. But I wanted to go even a step ahead. The project shims are nice but especially combined with TypeScript I don’t get the code completion support for these npm packages. And that`s what finally inspired me to this idea to make it possible to just include an npm package with close to zero configuration in my UI5 application by just using the npm package as a runtime dependency via sap.ui.define or import statements.
UI5 Tooling Extensions: ui5-tooling-modules
The solution for this is the development of a custom middleware and a custom task – the UI5 Tooling Extensions for NPM Package Consumption a.k.a. ui5-tooling-modules (GitHub, NPM) as part of the UI5 Ecosystem Showcase repository.
The task and middleware are configuration-free. This means, you just need to install the dependency ui5-tooling-modules and then declare the task and middleware in your ui5.yaml:
The custom task is scanning all AMD dependencies of all modules and tries to resolve the module names. If a module has been found a custom bundle will be created in the proper namespace of the module, e.g. @Apollo/client/core will create a custom bundle at dist/resources/@apollo/client/core.js.
The custom middleware is listening to incoming requests and checks those requests to match npm packages. E.g. a dependency to d3 will cause a request to resource/d3.js. The middleware now derives the module name which is "d3" and uses require.resolve("d3") to lookup the npm package for it. If an npm package exists, the middleware is using rollup to create a custom AMD bundle for the npm package which uses the UI5 AMD-like syntax sap.ui.define instead of define.
Remark: this solution may not work for all kind of npm packages. The npm packages should be frontend packages. Maybe in future the rollup configuration could be even customized to allow even more. Right now it is just using a predefined set of rollup plugins to bundle the npm packages.
Now, let’s make our fingers dirty!
Challenge: Add a PieChart into the OpenUI5 Sample App
Let’s use the OpenUI5 Sample Application as a starting point. The sample application is a Todo Application and I want to add a PieChart from Chart.js below showing the open/completed todos.
Install the Chart.js 3.7.1 npm package as dev dependency (the concrete version is used since Chart.js often introduces changes to their packaging which different code necessary to use it):
npm install chart.js@3.7.1 --save-dev
Create a custom control for the PieChart integration into UI5:
exit: function() {
if (this._chart) {
this._chart.destroy();
}
},
onBeforeRendering: function() {
if (this._chart) {
this._chart.destroy();
}
},
onAfterRendering: function() {
// determine the open/completed todos
var open = 0, completed = 0, todos = this.getTodos();
todos.forEach(function(todo) {
if (todo.completed) {
completed++;
} else {
open++;
}
});
A custom control is the glue code to connect OSS components with the UI5 programming model and its lifecycle. The custom control enables the usage of e.g. the PieChart natively in the XMLView and adds databinding support. The PieChart implementation is pretty primitive as the sample should mainly show-case the integration aspect of NPM packages - maybe someone wants to show-case a better PieChart implementation in another blog?
After creating the custom control, the next step is to extend the XMLView and embed the PieChart underneath the Todos List:
Therefore, use a VerticalLayout as content of the DynamicPage and put the List inside next to the newly created PieChart. Don't forget to add the namespace declarations to the View.
Now, you should be able to see the following result in your OpenUI5 Todo App:
Step 4: Connecting the dots...
To understand, how the UI5 Tooling Extensions for NPM Package Consumption works, check out the network trace. In the PieChart custom control, a dependency to "chart.js/auto" is declared. This causes a request to "resources/chart.js/auto.js" (the screenshot below shows the request to the chart.js.js which was related to an older version of Chart.js). The custom middleware "ui5-tooling-modules-middleware" handles the request and returns a UI5 AMD-like module:
Internally, in the custom middleware, the resource path is used to try to resolve a dependency from node_modules via "require.resolve". Once a dependency is found, Rollup is being used to generate a UI5 AMD-like bundle. And voilà: neither shimming on the UI5 Tooling nor shimming on the UI5 runtime is needed anymore.
Bonus Step 5: Building your project
Till now, the UI5 Tooling Extensions for NPM Package Consumption was only used for the development of your application. But for sure, being able to build a deployable application would be even nicer. To run the build for the application, just run the following command:
npm run build
After the build completed, inspect the "dist" folder and you can find a file called "chart.js.js":
This file is the custom bundle for the Chart.js. It has been generated by the custom task ui5-tooling-modules-task similar like the custom middleware does. But instead of listening per requests the custom tasks checks the used modules in all "sap.ui.define" and "sap.ui.require" calls of your application and tries to resolve them from node_modules.
Finally, this application can be deployed.
IMPORTANT: This concept only works out of the box for standalone UI5 applications. For all applications which will be deployed into a Fiori launchpad environment or another SAP system, the OSS libraries will not be loaded automatically as by default modules in the "resources" path will be resolved relative to the bootstrap script (typically sap-ui-core.js). To overcome this, a custom module path needs to be registered:
Now, the "chart.js" module will be loaded from "/yet/another/path/chart.js".
Wrap up
"One small step for a UI5er, one giant step for the UI5 community!". The consumption of OSS libraries becomes pretty simple and natural. The UI5 Tooling Extensions for NPM Package Consumption can also be used when doing TypeScript development with UI5. Together with my colleague Damian Maring, we demonstrated the consumption of Apollo GraphQL in UI5 with TypeScript during this years devtoberfest:
The UI5 Tooling Extensions are developed as part of the UI5 Ecosystem Showcase. Feel free to contribute, provide feedback and get inspired to create new ideas to improve our UI5 community more and more.
Nice! Any plans to add an option to influence the module resolving part (eg to specify a specific file within the dist directory of an dependency)? Would also be nice if this gets published to github anytime soon.
what exactly do you mean? It is possible to specify e.g. a nested module of a dependency, e.g. for "@apollo/client" you can directly use "@apollo/client/core". This is important since "@apollo/client" also has dependencies to react and core doesn't.
I'm wondering if we can influence what JS file(s) get(s) included from a dependency. I used SheetJS as an example where there are different generated files within the dist directory (https://github.com/SheetJS/sheetjs/tree/master/dist). I'm guessing the current use of
// try to resolve the module name from node modules (bare module)
const modulePath = require.resolve(moduleName);
includes the specified main file of the respective package.json. I'm wondering if we need the flexibility to change this (eg to include an already minified version of the dependency).
regarding your question, how you can access individual resources in an NPM package - the good thing is: require.resolve handles this. The advantage of require resolve is, that when requesting just the module name, it returns the main module and when you access a nested module you get this one.
This means you can refer to any module from a dependency. This should feel quite natural like for other modern apps.
Interesting is maybe one point: the usage of already minifies modules. Basically, this should work but as Rollup is being used to create the UI5 AMD-like bundles there could be some issues - but I cannot confirm definitely.
dotenv isn't really a frontend package IMO as it access environmental variables. I would rather use it at the server side. Due to the fact that there are packages which are not meant for client-side usage, I added the following remark:
Remark: this solution may not work for all kind of npm packages. The npm packages should be frontend packages. Maybe in future the rollup configuration could be even customized to allow even more. Right now it is just using a predefined set of rollup plugins to bundle the npm packages.
yes, it works there pretty similar. Just use imports instead of sap.ui.define|require. Recently, we created a tutorial for using TypeScript for UI5 and one exercise is the usage of NPM packages:
do you still face this issue? If so, maybe you can give a bit more context. Seems that there is a configuration issue somewhere or a bug? Please ensure to use the latest version of the UI5 Tooling and the latest version of the ui5-tooling-modules extensions.
Thank you for your quick response. I'm definitely trying this too. (I tried a solution with project shims already)
The issue I have (and I think it might be the same for the chart.js example) is: When I want to use npm packages that have their types included (like "moment").
I got it working for "lodash" and since it has it types in "@types/lodash" I think this will work.
Do you have any suggestions about using "moment" or npm packages that include their types? Or is "moment" one of the packages where this does not work (yet) ???
when developing UI5 Components the best place as of today might be the "Component.js". There you can add this code on top, outside of the sap.ui.define call, e.g.:
But right now, I do not use the typescript plugin in the rollup toolchain. Maybe it is worth to try to support this again. There might be a lot of cases for which this standard configuration will not work out and we should also allow to make the tooling extensions more flexible / configurable. This is also a call-out: if you have a good idea how to do so, feel free to elaborate this with me. For TS support we may also check the file extensions of the returned module of require.resolve. It's worth a try.
today evening I release a new version of the ui5-tooling-modules => 0.2.0. You may give it a try? At least loading the bundle for the tui-image-editor works but I did not make the UI working yet since I had not much time to look how the API should be used. Maybe you can share what issue you face here? Is there an error while loading the modules?
To get this running you need to also include the dependency to "tui-code-snippet". The variable name above suggests that rollup identified this as an external bundle. Try to add the dependency as well.
This one is related to a dependency to CSS in the filerobot-image-edior. Overall, this is an interesting question. How should we handle the assets of NPM packages. Ideally, they should be ignored from bundling but the CSS should be addressable so that it can be used by the consumers of those modules. But then, those dependencies should also be copied during the build process. I think this is something to mention as a limitation right now and we need to think how this could also be handled...
Can you also share the code which is used to create the TUI Image Editor with me? I don't see this issue on my side? Maybe the code I use is too basic?
One remark, the dependencies should be only in the "dependencies" section and not in the "devDependencies".
We can also follow up in a GH issue in the following repo:
Thanks a lot. But my problem is that I can't use yarn, because I don't have admin authorization on my PC. I try to check your solution in my project with npm. I copied tui-image-editor, updated
ui5-tooling-modules and added run script
But tui-image-editor.js doesn't served and I get an error.
Why tui-image-editor doesn't served? There are some difference between yarn and npm?
I just found this nice and very precise tutorial of your. Unfortunately even after following every step twice i still just get 404 for chart.js.js followed by:
ModuleError: Failed to resolve dependencies of 'sap/ui/demo/todo/control/PieChart.js'
-> 'chart.js.js': failed to load 'chart.js.js' from resources/chart.js.js: script load error
It would seem to me the "modules" stuff is simply not kicking in. However i went over the yaml / package config several times and cant find any issue.
Do you have some advice for how to fix this / investigate further?
I tried this method, it worked for pure ui5 project. But currently, if we develop UI5 in BTP, we will use MTA project. Currently the easy-ui5 3.0 also generate MTA project. I tried this with MTA ui5 model, it does not work. Is it possible adopt this method with easy-ui 3.0 generated project?
I have tried easy-ui 3.0 generated project again, I can see the generated js file under resources folder . But it seems the middleware ui5-middleware-cfdestination always search the third party library from https://ui5.sap.com/resources even I change parameter allowLocalDir to true.
Resolved the issue of always getting the libs from openui5 websites . The application will get libs from local resources folder . But still getting error . I have used cmis. cmis lib shown in folder resources folder . But when run the ui5 application , the error is like the following: Another issue, the tool does not recognize the char.js lib which used in your blog .
-> 'cmis.js': failed to execute module factory for ''cmis.js'': exports is not defined
at makeModuleError (http://localhost:8080/resources/ui5loader.js:1042:15)
at Module.failWith (http://localhost:8080/resources/ui5loader.js:814:13)
at http://localhost:8080/resources/ui5loader.js:1858:32
Caused by: ModuleError: failed to execute module factory for ''cmis.js'': exports is not defined
sorry for my late reply. I updated the usage of Chart.js in the blog post above and hardened it to 3.7.1. Since Chart.js often changed the consumption of their package even in minor version updates, I decided to keep harden the version to keep this blog post working for the future.
Regarding the cmis.js OSS, I need to check why this happens. Not all OSS libraries can be properly handled by the ui5-tooling-modules tooling extensions and may require to use the shimming instead. Especially also if additional JS or assets are loaded lazily later on, the usage of shimming is the better alternative. I'm thinking of updating the sample projects to include one OSS via shimming.
But I have a small example which I may upload as an example to your issue on the ecosystem:
Thank you very much! It works with your suggestion CMIS . I will continue to test CMIS in UI5 . By the way . I am not sure if this method worked for sap cloud sdk . If we can make sap cloud sdk worked in sap ui5 directly . It will greatly simplify s4 side by side extension . Anyway I will test .
Just share with you about the status of testing CMIS in UI5 . If I run the following sentence , the function loadRepositories will get the correct content from BTP cmis . Sentence of query will ends with CORS error ,whatever route I maintained to route the call to destination .
let session = new cmis.CmisSession('/sdi/browser');
session.loadRepositories()
.then(() => session.query("select * from cmis:document"))
.then(data =>console.log(data));
I just tried to deploy your app (without making any change, only wrapping it into a mta project) to my trial Cloud Foundry environment. However I ended up with below issues (basically chart.js.js does not seem to be seen by the Launchpad service). The project locally works perfectly fine. Would you have any comment onto that? Am I missing something or is this functionality not yet available on Launchpad?
Log-dbg.js:493 2023-03-19 21:00:49.155100 The issue is most likely caused by application sap.ui.demo.todo. Please create a support incident and assign it to the support component of the respective application. - Failed to load UI5 component with properties: '{
"name": "sap.ui.demo.todo",
"url": "/aa36af2e-05d8-4272-9ea0-e30e13e9f901.sapuidemotodo.sapuidemotodo/~190323195602+0000~/",
"manifest": true,
"asyncHints": {
"libs": [
{
"name": "sap.m",
"lazy": false
},
{
"name": "sap.ui.unified",
"lazy": false
},
{
"name": "sap.f",
"lazy": false
},
{
"name": "sap.ui.fl",
"lazy": false
}
],
"components": [],
"waitFor": []
},
"messages": [],
"version": "0.0.1",
"id": "application-demo-display-component",
"componentData": {
"startupParameters": {}
}
}'. Error likely caused by:
ModuleError: Failed to resolve dependencies of 'sap/ui/demo/todo/control/PieChart.js'
-> 'chart.js.js': failed to load 'chart.js.js' from https://sapui5.hana.ondemand.com/1.111.1/resources/chart.js.js: 404
at ve (https://sapui5.hana.ondemand.com/1.111.1/resources/sap/fiori/appruntime-min-0.js:15:7732)
at de.failWith (https://sapui5.hana.ondemand.com/1.111.1/resources/sap/fiori/appruntime-min-0.js:15:5570)
at https://sapui5.hana.ondemand.com/1.111.1/resources/sap/fiori/appruntime-min-0.js:15:17950
at Ie (https://sapui5.hana.ondemand.com/1.111.1/resources/sap/fiori/appruntime-min-0.js:15:16420)
at Oe (https://sapui5.hana.ondemand.com/1.111.1/resources/sap/fiori/appruntime-min-0.js:15:17906)
at Object.Me [as define] (https://sapui5.hana.ondemand.com/1.111.1/resources/sap/fiori/appruntime-min-0.js:15:18975)
at sap/ui/demo/todo/control/PieChart.js (https://780f5f1etrial.launchpad.cfapps.us10.hana.ondemand.com/aa36af2e-05d8-4272-9ea0-e30e13e9f901.sapuidemotodo.sapuidemotodo/~190323195602+0000~/Component-preload.js:7:8)
Thanks for the blog. I have followed this to integrate xlsx library in a freestyle SAPUI5 application. It runs perfectly in dev environment. I have deployed the app in CF with resources directory containing xlsx.js.
This directory structure is also deployed in CF
I have maintained the path for xlsx.js.
Path for xlsx.js
It does not seem to be picking xlsx.js from this directory and I am getting the following error when I run the deployed app.
to "./resources/libname" in the Component.js it allowed us to use the CDN link together with local npm resources, increasing the performance of our app 🙂
Right now, If I create a sapui5 project using vscode, then I automatically adds the ui5 tooling, but not the builder part. That means we can just install any npm library and then use it directly?
I try to set this up in a Fiori Elements FLP component and I cannot figure out what path I need to specify in the call of
sap.ui.loader.config. I installed chart.js as dev dependency with like you did.
npm install chart.js@3.7.1 --save-dev
So it is in node_modules, but somehow I cannot point to it. At the top of my Component.js I have:
sap.ui.loader.config({
paths: {
"chart.js":"../node_modules/chart.js"
}
});
And then I try to consume it from a custom control like in the tutorial:
sap.ui.define([
"sap/ui/core/Control",
"chart.js/auto"
], (Control, Chart) => { ... })
The error that I get in the console contains: 'chart.js/auto.js': failed to load 'chart.js/auto.js' from ../../node_modules/chart.js/auto.js: script load error
I don't really understand why in the example we load "chart.js/auto", because it is a folder. As one point the loader assumes auto is a js file it seems.
I think I need to make an update of the blog post soon since there has been so much happened over the last month inside the ui5-tooling-modules package.
You cannot point to node modules directly - the resources need to be loaded relative to your application. The version 3.x of the ui5-tooling-modules has a built-in feature called addToNamespace which copies the OSS packages into the thirdparty package of your application/library and then you do not need to maintain any sap.ui.loader.config option.
If you want to use the sap.ui.loader.config way - just take a look into your build output. You'll find the chart.js resources inside the dist/resources folder. Those resources normally conflicts with the resources loaded relative to the UI5 bootstrap and the simplest ways to redirect it to the local ones is to add the sap.ui.loader.config pointing to:
sap.ui.loader.config({
paths: {
"chart.js":"./resources/chart.js"
}
});
But this is only a solution if you are owning the index.html - I would recommend you to try the addToNamespace option.
And why do we point to chart.js/auto -> because it is explained here: https://www.chartjs.org/docs/latest/getting-started/integration.html - normally the require resolves the index.js module inside folders and the bundler of the ui5-tooling-modules reuses this concept so that it behaves exactly like explained in the documentations of thirdparty libs, so you just need to require it like this: sap.ui.require(["chart.js/auto"], function(ChartJS) { ... });