EDUCAÇÃO E TECNOLOGIA

Using NPM Packages in UI5 without Shims


Introduction

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:

UI5%20Tooling%20Ecosystem

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:

Task declaration:

builder: customTasks: - name: ui5-tooling-modules-task afterTask: replaceVersion

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.

Middleware declaration:

server: customMiddleware: - name: ui5-tooling-modules-middleware afterMiddleware: compression

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.

The final code of the following challenge can be found in the following GitHub pull request: https://github.com/petermuessig/openui5-sample-app/pull/1

Step 1: Prepare your environment

First, you need to clone the repository and install the dependencies:

git clone https://github.com/SAP/openui5-sample-app.git
cd openui5-sample-app
npm install

Run the application with the following command:

ui5 serve -o index.html

You can open the openui5-sample-app via: http://localhost:8080/index.html

Hint: if you want to improve your development experience with a livereload feature, just add the ui5-middleware-livereload or follow the blog post: “UI5 Tooling: a modern development experience for UI5!”.

Step 2: Configure the ui5-tooling-modules extensions

Install the ui5-tooling-modules extensions as dev dependency to the project:

npm install ui5-tooling-modules --save-dev

Open your package.json and add the following lines:

{ "name": "openui5-sample-app", […] "devDependencies": { […] "ui5-tooling-modules": "^0.1.0" }, "ui5": { "dependencies": [ "ui5-tooling-modules" ] }
}

Open the ui5.yaml and add the following lines:

specVersion: '2.0'
[…]
builder: customTasks: - name: ui5-tooling-modules-task afterTask: replaceVersion
server: customMiddleware: - name: ui5-tooling-modules-middleware afterMiddleware: compression

Step 3: Add Chart.js to the project

Install the Chart.js npm package as dev dependency:

npm install chart.js --save-dev

Create a custom control for the PieChart integration into UI5:

sap.ui.define([ "sap/ui/core/Control", "chart.js"
], function(Control, Chart) { "use strict"; return Control.extend("sap.ui.demo.todo.control.PieChart", { metadata: { properties: { todos: {type: "object"} } }, 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++; } }); // render the chart this._chart = new Chart(this.getDomRef(), { type: 'pie', responsive: false, data: { labels: [ 'Open', 'Completed' ], datasets: [{ label: 'Open/Completed Todos', data: [open, completed], backgroundColor: [ 'rgb(255, 99, 132)', 'rgb(54, 162, 235)' ], hoverOffset: 4 }] } }); }, renderer: { apiVersion: 2, render: function(oRm, oControl) { oRm.openStart("canvas", oControl); oRm.style("margin", "5em"); oRm.openEnd(); oRm.close("canvas"); } } }); });

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:

<mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns:layout="sap.ui.layout" xmlns:cc="sap.ui.demo.todo.control" ... /> [...] <f:DynamicPage> [...] <f:content> <layout:VerticalLayout> <List ... /> <cc:PieChart todos="{/todos}"/> </layout:VerticalLayout> </f:content> [...] </f:DynamicPage> [...]
</mvc:View>

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” is declared. This causes  a request to “resources/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”:

openui5-sample-app
├── dist ├── resources ├── chart.js.js <-- the custom bundle for Chart.js ... └── ui5loaader.js ... └── manifest.json
...
└── ui5.yaml

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:

sap.ui.loader.config({ paths: { "chart.js": "/yet/another/path/chart.js" }
});

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:

GitHub: https://github.com/petermuessig/ui5-sample-apollo-reloaded/

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.