Technical Articles
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:
- prelude
- dev-time: controls as npm modules
- addendum: build time for the app
- naming convention: ui5-cc-$name.space
π₯ 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).
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")
}
}
})
})
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>
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 namespacemd
ui5-cc-hello.world
βΒ uses namespacehello.world
for included custom controls
π Sooo, now, go go go and create and use awesome UI5 custom controls via npm!
This is huge step to the reusability world, can't wait to try it out, thanks!
Simply amazing!
someone recently asked, why in the code-sample from ui5-cc-md...
... the
marked
Β part is commented out: this is due how the referencedmarked.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 inwindow.marked
anyway.hth, v.
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
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?
Hi Volker,
adding
resourceRoots
to thesap.ui5
section of manifest.json helped to solve this issue.Hey George Younan π Can I ask how you managed to use an external library from npm with the open described way?!
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