Custom UI5 Builder Tasks¶
The UI5 Build Extensibility enables you to enhance the build process of any UI5 project. In addition to the standard tasks, custom tasks can be created.
The UI5 community already created many custom tasks which you can integrate into your project. They are often prefixed by ui5-task-
to make them easily searchable in the npm registry.
Please note that custom tasks from third parties can not only modify your project but also execute arbitrary code on your system. In fact, this is the case for all npm packages you install. Always act with the according care and follow best practices.
Configuration¶
You can configure your build process with additional build task. These custom tasks are defined in the project configuration.
To hook your custom tasks into the different build phases of a project, they need to reference other tasks to be executed before or after. This can be a standard task or another custom task. Note that a custom task will only be executed if the referenced task is executed (i.e. is not disabled).
In the below example, when building the library my.library
the custom babel
task will be executed before the standard task generateComponentPreload
.
Another custom task called generateMarkdownFiles
is then executed immediately after the standard task uglify
.
Example: Basic configuration¶
# In this example configuration two custom tasks are defined: 'babel' and 'generateMarkdownFiles'.
specVersion: "2.6"
type: library
metadata:
name: my.library
builder:
customTasks:
- name: babel
beforeTask: generateComponentPreload
- name: generateMarkdownFiles
afterTask: uglify
configuration:
color: blue
Example: Connect multiple custom tasks¶
You can also connect multiple custom tasks with each other. The order in the configuration is important in this case. You have to make sure that a task is defined before you reference it via beforeTask
or afterTask
.
# In this example 'myCustomTask2' gets executed after 'myCustomTask1'.
specVersion: "2.6"
type: library
metadata:
name: my.library
builder:
customTasks:
- name: myCustomTask1
beforeTask: generateComponentPreload
- name: myCustomTask2
afterTask: myCustomTask1
Custom Task Extension¶
A custom task extension consists of a ui5.yaml
and a task implementation. It can be a standalone module or part of an existing UI5 project.
Example: ui5.yaml¶
specVersion: "2.6"
kind: extension
type: task
metadata:
name: generateMarkdownFiles
task:
path: lib/tasks/generateMarkdownFiles.js
Task extensions can be standalone modules which are handled as dependencies.
Alternatively you can implement a task extension as part of your UI5 project. In that case, the configuration of the extension is part of your project configuration inside the ui5.yaml
as shown below.
The task extension will then be automatically collected and processed during the processing of the project.
Example: Custom Task Extension defined in UI5 project¶
# Project configuration for the above example
specVersion: "2.6"
kind: project
type: library
metadata:
name: my.library
builder:
customTasks:
- name: generateMarkdownFiles
afterTask: uglify
configuration:
color: blue
---
# Task extension as part of your project
specVersion: "2.6"
kind: extension
type: task
metadata:
name: generateMarkdownFiles
task:
path: lib/tasks/generateMarkdownFiles.js
Task Implementation¶
A custom task implementation needs to return a function with the following signature:
/**
* Custom task example
*
* @param {object} parameters Parameters
* @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write resources
* @param {module:@ui5/fs.AbstractReader} parameters.dependencies ReaderCollection to read dependency resources
* @param {object} parameters.taskUtil Specification Version dependent interface to a
* [TaskUtil]{@link module:@ui5/builder.tasks.TaskUtil} instance
* @param {object} parameters.options Options
* @param {string} parameters.options.projectName Project name
* @param {string} [parameters.options.projectNamespace] Project namespace
* @param {string} [parameters.options.configuration] Task configuration if given in ui5.yaml
* @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written or rejecting in case of an error
*/
module.exports = async function({workspace, dependencies, taskUtil, options}) {
// [...]
};
Parameters:
workspace
: A DuplexCollection to read and write Resources for the project that is currently being builtdependencies
: A ReaderCollection to read Resources of the project's dependenciestaskUtil
: See details belowoptions.projectName
: The name of the project currently being built. Example:my.library
options.projectNamespace
: The namespace of the project. Example:my/library
options.configuration
: The task configuration as defined in the project's ui5.yaml. See Configuration
Returns:
A Promise that resolves once the task has completed and all new or modified resources have been written to the workspace.
In case of errors the promise should reject with an Error object, causing the build to abort.
Warning
Depending on your project setup, UI5 Tooling tends to open many files simultaneously during a build. To prevent errors like EMFILE: too many open files
, we urge custom task implementations to use the graceful-fs module as a drop-in replacement for the native fs
module.
Example: lib/tasks/generateMarkdownFiles.js¶
The following code snippet shows an example of what a task implementation might look like. This task uses a generic "renderMarkdown" library to transform markdown files to HTML and writes out the newly created markdown files.
const path = require("path");
const {Resource} = require("@ui5/fs");
const renderMarkdown = require("./renderMarkdown");
module.exports = async function({workspace, dependencies, taskUtil, options}) {
const textResources = await workspace.byGlob("**/*.md")
await Promise.all(textResources.map(async (resource) => {
const htmlString = await renderMarkdown(await resource.getString());
const markdownResourcePath = resource.getPath();
// Note: @ui5/fs virtual paths are always POSIX (on all systems)
const newResourceName = path.posix.basename(markdownResourcePath, ".md") + ".html";
const newResourcePath = path.posix.join(path.posix.dirname(markdownResourcePath), newResourceName);
const markdownResource = new Resource({
path: newResourcePath,
string: htmlString
})
await workspace.write(markdownResource);
}));
};
Example: lib/tasks/bundlesOnly.js¶
The following code snippet shows an example of a custom task, filtering for resources that are not bundles and tagging them for being omitted from the build result.
module.exports = async function({workspace, dependencies, taskUtil, options}) {
const jsResources = await workspace.byGlob("**/*.js")
jsResources.forEach((resource) => {
if (!taskUtil.getTag(resource, taskUtil.STANDARD_TAGS.IsBundle)) {
// Resource is not a Bundle => Remove it from the build result
taskUtil.setTag(resource, taskUtil.STANDARD_TAGS.OmitFromBuildResult);
}
});
};
Helper Class TaskUtil
¶
Custom tasks defining Specification Version 2.2 or higher have access to an interface of a TaskUtil instance.
In this case, a taskUtil
object is provided as a part of the custom task's parameters. Depending on the specification version of the custom task, a set of helper functions is available to the implementation. The lowest required specification version for every function is listed in the TaskUtil API reference.
Also see UI5 Tooling RFC 0008 Resource Tagging During Build for details on resource tagging.