Skip to Content
Technical Articles
Author's profile photo Volker Buzek

distribute and reuse UI5 custom controls via npm

tl;dr: the ui5-tooling supports using custom controls from the npm realm, for both development- and build-time. This makes re-use and distribution of custom controls easier than wrapping them in a library.

in here:

πŸ”₯ A barebones example repo is at https://github.com/vobu/ui5-npm-custom-control

prelude

Until now, the primary means of reusing UI5 custom controls was to wrap them in a UI5 library and include that library in another UI5 app.
But next to application and (theme-)library, the ui5-tooling also allows for a type module. This describes a generic …well.. “module” that can be attached to a namespace (“resource root”, in UI5 speak).

# packages/ui5-cc-hello.world/ui5.yaml
specVersion: "2.2"
type: module
metadata:
  name: ui5-cc-hello.world

resources:
  configuration:
    paths:
      "/resources/cc/hello/world/": "./" # note the trailing slashes!

Above noted path configuration tells the UI5 runtime to look for runtime artefacts of the namespace cc.hello.world in the current folder. (The resources part in the path is a requirement by the ui5-tooling.)

Subsequently, that namespace can be used by the UI5 runtime to include Controls, here: namespace cc.hello.world, Control Greeter(.js).

network%20request%20for%20the%20custom%20control

dev-time: controls as npm modules

A UI5 custom control consists of the module logic and a renderer. They can either be split (e.g. look at sap.m.Text.js and sap.m.TextRenderer.js) or exist in the same file, using a static renderer function:

// packages/ui5-cc-hello.world/Greeter.js
sap.ui.define(["sap/ui/core/Control"], (Control) => {
    return Control.extend("cc.hello.world.Greeter", {
        renderer: {
            apiVersion: 2,
            render(oRM, oControl) {
                oRM.openStart("p", oControl)
                oRM.openEnd()
                oRM.text("UI5 custom control: Hello World!")
                oRM.close("p")
            }
        }
    })
})

html%20output%20of%20the%20custom%20control

A UI5 type module can be transferred into an npm module by simply giving it an npm init:

// file system view
ui5-cc-hello.world
β”œβ”€β”€ Greeter.js
β”œβ”€β”€ package.json
└── ui5.yaml

// package.json
{
  "name": "ui5-cc-hello.world",
  "version": "0.0.1",
  "description": "UI5 custom (notepad) control demo",
  "license": "Beerware"
}

By combining the ui5-tooling descriptor ui5.yaml with a package.json, the UI5 custom control now is the regular npm module ui5-cc-hello.world πŸ₯³ – and can be released and required as such!

hint: already note the naming convention

​ ui5-cc-$name.space

for UI5 controls in npm-verse.

npm-based UI5 custom control in an app

In a UI5 app, it’s now as easy as requiring the custom control via dependencies and ui5.dependencies

// in packages/ui5-app/package.json
"dependencies": {
        "ui5-cc-hello.world": "0.0.1"
    },
"ui5": {
    "dependencies": [
        "ui5-cc-hello.world"
    ]
}

…and using the namespace and control in a view:

<!-- packages/ui5-app/webapp/indexBareBones.html -->
<!-- in xml template literal -->
<mvc:View xmlns:hello.world="cc.hello.world">
    <hello.world:Greeter />            
</mvc:View>

custom%20control%20in%20xml%20view%20of%20a%20ui5%20app

There’s also no need to fiddle with data-sap-ui-resourceroots in the bootstrap html – the ui5-tooling automatically serves resources/**/* to the application.

A note on theming πŸ› : while UI5 libraries generally contain support for themes, the here presented npm-custom control approach can only include theme-specific optics by using CSS variables. Bespoken CSS variables are recognized by the ui5-tooling at development time! Please dig into the blog post by UI5 chieftain Peter Müßig for how to use CSS variables in your UI5 app – and thus, in UI5 custom controls.

3rd party modules in custom control

Custom controls might need 3rd party npm modules at runtime, most likely to re-use functionality already out there, and not having to reinvent the wheel(s).

Fortunately, shims from the ui5-tooling can be used for achieving just that: using npm modules with UI5 (see this nice blog post by Vivek C.). And the same concept can be applied to npm-based custom controls as well.

First, the 3rd party modules needs to be included into the custom control by standard npm mechanism npm install $dependency, resulting in a dependencies entry into package.json.

// from: ui5-cc-md
"dependencies": {
    "marked": "^1.2.9"
}

Then, use ui5.yaml‘s project-shim to patch the module through to the custom control at runtime:

# also from ui5-cc-md
specVersion: "2.2"
type: module
metadata:
  name: ui5-cc-md

resources:
  configuration:
    paths:
      "/resources/cc/md/": "./"
---
specVersion: "2.2"
kind: extension
type: project-shim
metadata:
  name: marked
shims:
  configurations:
    "marked":
      specVersion: "2.2"
      type: module
      metadata:
        name: "marked"
      resources:
        configuration:
          paths:
            "/resources/cc/md/marked/": "./"

The above extension provides the 3rd party module marked from the custom control’s npm_modules folder in the same namespace as the custom control itself (cc.md), at the path /resources/cc/md/marked/.

The acutal marked module code can subsequently be loaded in the custom control via the standard sap.ui.define:

sap.ui.define(["./marked/marked.min"], (/* marked */) => { /*...*/ })

No more need to copy/paste 3rd party source code into the custom control realm!

addendum: build time for the app

When developing custom controls, there’s generally no need for build artefacts. Once the code runs as desired, the ui5 custom control can be published to npm or any other registry, and is ready to use. Optimizations -such as providing a minified, runtime-optimized version and a -dbg.js human readable counterpart, are possible, but not required.

From the viewpoint of the application the npm-distributed custom control is used in, a “standalone” build is necessary to make the app -including the custom control(s)- run in enviornments other than the ui5-tooling. By doing ui5 build --all , the npm-based custom controls are copied into the dist/resources folder (including all shims!) and are available for deployment.

$> ui5 build --all
info normalizer:translators:ui5Framework Using OpenUI5 version: 1.86.3
info builder:builder Building project ui5-app including dependencies...
info builder:builder πŸ›   (1/9) Building project ui5-cc-hello.world
info builder:builder πŸ›   (2/9) Building project marked
info builder:builder πŸ›   (3/9) Building project ui5-cc-md
# ...

This results in a dist folder containing all custom controls and shims:

# sample app incl ui5-cc-md + ui5-cc-hello.world
ui5-app/dist/resources
β”œβ”€β”€ cc
β”‚   β”œβ”€β”€ hello
β”‚   β”‚   └── world
β”‚   β”‚       β”œβ”€β”€ Greeter.js
β”‚   β”‚       β”œβ”€β”€ package.json
β”‚   β”‚       └── ui5.yaml
β”‚   └── md
β”‚       β”œβ”€β”€ Markdown.js
β”‚       β”œβ”€β”€ marked
# snip
β”‚       β”‚   β”œβ”€β”€ marked.min.js
β”‚       β”‚   β”œβ”€β”€ package.json
β”‚       β”‚   └── src
# snip
β”‚       β”‚       └── rules.js
β”‚       β”œβ”€β”€ package.json
β”‚       └── ui5.yaml
# ...

naming convention: ui5-cc-$name.space

As with community-driven UI5 tasks (“ui5-task-$task”) and middlewares (“ui5-middleware-$middleware”), we propose to use the naming schema

​ ui5-cc-$name.space

for custom controls on public npm registries.

With the prefix, it makes searching for custom UI5 controls easier. And by including the namespace in the npm module name, potential naming conflicts should be avoidable upfront.

Examples:

  • ui5-cc-md β†’ only holds 1 custom control, Markdown, in namespace md
  • ui5-cc-hello.world β†’Β uses namespace hello.world for included custom controls

πŸš€ Sooo, now, go go go and create and use awesome UI5 custom controls via npm!

Assigned Tags

      8 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Denis Galand
      Denis Galand

      This is huge step to the reusability world, can't wait to try it out, thanks!

      Author's profile photo Somnath Paul
      Somnath Paul

      Simply amazing!

      Author's profile photo Volker Buzek
      Volker Buzek
      Blog Post Author

      someone recently asked, why in the code-sample from ui5-cc-md...

      sap.ui.define(["./marked/marked.min"], (/* marked */) => { /*...*/ })

      ... the markedΒ part is commented out: this is due how the referenced marked.minΒ is exporting marked at runtime, namely directly into the global scope. So no need to import the source as a dedicated var, as it is in window.markedanyway.

      hth, v.

       

      Author's profile photo George Younan
      George Younan

      Hi Volker,

      Thanks for the great blog. It's good to see a possibility to use npm packages for reuse scenarios in UI5. We tried it out for one of our libraries and it works perfectly in standalone mode. Alas, it does not work when the consuming app runs in Launchpad. The resources folder is not loaded when the app is initially loaded and we get an error the the library is not found. Did you face such issues when running in Launchpad as well?

      Best regards,
      George

       

      Author's profile photo Volker Buzek
      Volker Buzek
      Blog Post Author

      hi, i have explicitly only used this with custom controls, not with libraries yet.

      pinging Geert-Jan KlapsΒ - maybe you can chime in and help?

      Author's profile photo George Younan
      George Younan

      Hi Volker,

      adding resourceRoots to the sap.ui5 section of manifest.json helped to solve this issue.

       

       

      Author's profile photo Carsten Michels
      Carsten Michels

      Hey George Younan πŸ™‚ Can I ask how you managed to use an external library from npm with the open described way?!

       

       

      Author's profile photo George Younan
      George Younan

      Hi Carsten,

      I had to add the path to the third-party library to resourceRoots. In standalone mode this need to be done in index.html. For Launchpad this needs to be set in manifest.json.

      Best Regards,

      George