Expected Behavior
The buildThemes task should only be re-executed by the incremental (delta) build system
when an input that actually affects its output changes (e.g. a library.source.less file, or
a .library / library.js file being added or removed, which changes the set of
directories a theme is built for).
A change to the content of a .library or library.js file — while the file continues to
exist in the same directory — must not invalidate the buildThemes cache, because the task
does not read the content of these files.
Current Behavior
The buildThemes task is re-executed on any content change to a .library or library.js
file within the project (or its dependencies), even though the theme output cannot possibly
differ.
Root cause: For non-root library projects, buildThemes is configured with a
librariesPattern glob (packages/project/lib/build/definitions/library.js and
themeLibrary.js):
librariesPattern: !taskUtil.isRootProject() ? "/resources/**/(*.library|library.js)" : undefined,
The task requests these files via combo.byGlob(librariesPattern)
(packages/builder/lib/tasks/buildThemes.js) and uses them only to derive the set of
directories that contain a library:
(await pAvailableLibraries).forEach((resource) => {
const library = path.dirname(resource.getPath()); // only the PATH is used
if (!availableLibraries.includes(library)) {
availableLibraries.push(library);
}
});
The resource content is never read. However, the incremental build cache tracks every
resource a task requests by its content hash (integrity), so the buildThemes stage
signature incorporates the content of every matched .library / library.js file. Any content
change to such a file therefore changes the stage signature and forces buildThemes to
re-execute — a needless cache miss that slows down incremental rebuilds in watch/serve mode.
Note: The over-tracking is by content hash, so a change that is later stripped (e.g. a JS
comment removed by minify, whose output buildThemes reads) does not trigger the rebuild.
Only changes that survive into the workspace buildThemes reads (e.g. a changed return value in
library.js, or any content change in the non-minified .library) cause the spurious
re-execution.
Steps to Reproduce the Issue
- Serve an application with a non-root library dependency that has a theme
(fixture application.a → library.a, which contains
themes/base/library.source.less).
- Add a
library.js next to the library's .library file and warm the build cache by
requesting a resource.
- Change only the content of
library.js in a way that survives minification (e.g. change
a returned value), and request a theme resource
(/resources/library/a/themes/base/library.css).
- Observe the emitted
ui5.project-build-status events for library.a: buildThemes fires
task-start / task-end (re-executed) instead of task-skip.
The following integration test (using the BuildServer harness with a mocked @parcel/watcher)
reproduces the issue deterministically, independent of the OS-level file watcher:
test.serial("Serve application.a, library.js content change must not re-run buildThemes", async (t) => {
const fixtureTester = t.context.fixtureTester = await FixtureTester.create(t, "application.a");
await fixtureTester.serveProject();
// Add a library.js to library.a. It is matched by the buildThemes `librariesPattern` glob, but
// buildThemes uses only its path, not its content.
const libraryJsPath =
`${fixtureTester.fixturePath}/node_modules/collection/library.a/src/library/a/library.js`;
await fs.writeFile(libraryJsPath, `sap.ui.define([], function() {\n\t"use strict";\n\treturn 1;\n});\n`);
await fixtureTester.fireWatcherEvent("create", libraryJsPath);
// #1 initial request builds library.a (and its dependencies), warming the cache. Request the
// theme output itself so it is unambiguous that the later request (#3) is a rebuild of an
// already-built resource rather than a first build.
await fixtureTester.requestResource({resource: "/resources/library/a/themes/base/library.css"});
// #2 request again with a warm cache — nothing rebuilds.
await fixtureTester.requestResource({
resource: "/resources/library/a/themes/base/library.css",
assertions: {projects: {}},
});
// Change ONLY the content of library.js — buildThemes never reads this content. Use a change
// that survives minification (a returned value) so the resource buildThemes tracks really differs.
await fs.writeFile(libraryJsPath, `sap.ui.define([], function() {\n\t"use strict";\n\treturn 42;\n});\n`);
await fixtureTester.fireWatcherEvent("update", libraryJsPath);
// #3 rebuild after the change. buildThemes must be skipped for library.a — the changed file's
// content is irrelevant to it. This assertion FAILS today: buildThemes is missing from the
// skipped set because it is spuriously re-executed.
await fixtureTester.requestResource({
resource: "/resources/library/a/themes/base/library.css",
assertions: {
projects: {
"library.a": {
skippedTasks: [
"buildThemes",
"enhanceManifest",
"escapeNonAsciiCharacters",
"replaceBuildtime",
]
}
}
}
});
});
To run it:
npm ci --engine-strict
npx ava test/lib/build/BuildServer.integration.js \
--match="*library.js content change must not re-run buildThemes*"
(run from packages/project)
Context
- UI5 Module Version (output of
ui5 --version when using the CLI): 5.0.0-alpha.7
- Node.js Version:
26.5.0
- npm Version:
11.19.0
- OS/Platform:
macOS 26.6.1 (darwin)
- Browser (if relevant):
unknown
- Other information regarding your environment (optional): Reproduced via the
BuildServer integration test harness (mocked @parcel/watcher), so it is independent of
the OS-level file watcher.
Expected Behavior
The
buildThemestask should only be re-executed by the incremental (delta) build systemwhen an input that actually affects its output changes (e.g. a
library.source.lessfile, ora
.library/library.jsfile being added or removed, which changes the set ofdirectories a theme is built for).
A change to the content of a
.libraryorlibrary.jsfile — while the file continues toexist in the same directory — must not invalidate the
buildThemescache, because the taskdoes not read the content of these files.
Current Behavior
The
buildThemestask is re-executed on any content change to a.libraryorlibrary.jsfile within the project (or its dependencies), even though the theme output cannot possibly
differ.
Root cause: For non-root library projects,
buildThemesis configured with alibrariesPatternglob (packages/project/lib/build/definitions/library.jsandthemeLibrary.js):The task requests these files via
combo.byGlob(librariesPattern)(
packages/builder/lib/tasks/buildThemes.js) and uses them only to derive the set ofdirectories that contain a library:
The resource content is never read. However, the incremental build cache tracks every
resource a task requests by its content hash (
integrity), so thebuildThemesstagesignature incorporates the content of every matched
.library/library.jsfile. Any contentchange to such a file therefore changes the stage signature and forces
buildThemestore-execute — a needless cache miss that slows down incremental rebuilds in watch/serve mode.
Note: The over-tracking is by content hash, so a change that is later stripped (e.g. a JS
comment removed by
minify, whose outputbuildThemesreads) does not trigger the rebuild.Only changes that survive into the workspace
buildThemesreads (e.g. a changed return value inlibrary.js, or any content change in the non-minified.library) cause the spuriousre-execution.
Steps to Reproduce the Issue
(fixture
application.a→library.a, which containsthemes/base/library.source.less).library.jsnext to the library's.libraryfile and warm the build cache byrequesting a resource.
library.jsin a way that survives minification (e.g. changea returned value), and request a theme resource
(
/resources/library/a/themes/base/library.css).ui5.project-build-statusevents forlibrary.a:buildThemesfirestask-start/task-end(re-executed) instead oftask-skip.The following integration test (using the
BuildServerharness with a mocked@parcel/watcher)reproduces the issue deterministically, independent of the OS-level file watcher:
To run it:
npm ci --engine-strict npx ava test/lib/build/BuildServer.integration.js \ --match="*library.js content change must not re-run buildThemes*"(run from
packages/project)Context
ui5 --versionwhen using the CLI):5.0.0-alpha.726.5.011.19.0macOS 26.6.1 (darwin)unknownBuildServerintegration test harness (mocked@parcel/watcher), so it is independent ofthe OS-level file watcher.