Skip to content

feat: Add off mode to UI5_WATCH_MODE - #1565

Open
RandomByte wants to merge 3 commits into
mainfrom
feat/ui5-watch-mode-off
Open

RandomByte wants to merge 3 commits into
mainfrom
feat/ui5-watch-mode-off

Conversation

@RandomByte

@RandomByte RandomByte commented Sep 2, 2026 •

Copy link
Copy Markdown
Member

This flag allows to disable file watching, e.g. in CI and other environments where sources do not change and the overhead (especially in polling mode) is unwanted.

JIRA: CPOUI5FOUNDATION-1355

@RandomByte
RandomByte force-pushed the feat/ui5-watch-mode-off branch 4 times, most recently from ce6fc6d to da7da79 Compare September 22, 2026 13:46
In CI and other environments where sources do not change while the server
runs, file watching adds cost without benefit. The polling backend is the most
expensive: it walks the source tree every 250 ms, and it is the default inside
containers, where CI commonly runs.

UI5_WATCH_MODE=off makes the fileWatcher facade's subscribe() return an inert
subscription: the callback is never invoked and unsubscribe() is a no-op, so no
backend is loaded and no filesystem is polled. All three watcher consumers
(WatchHandler, ProjectDefinitionWatcher, projectGraphSettleWatcher) go through
this facade, so none of them starts an OS watch handle or a poll loop. Source
changes no longer trigger rebuilds or live reload while the server runs.

The backend decision memoizes a mode string ("native" | "polling" | "off")
instead of a boolean. shouldUsePolling() and the new isWatchingDisabled()
derive from it. The "off" selection is logged at verbose, consistent with the
polling and native modes.

Expose fileWatcher through the package's internal exports so @ui5/cli can
import isWatchingDisabled(), matching the ./internal/... export pattern the CLI
already uses for ProjectDefinitionWatcher.

JIRA: CPOUI5FOUNDATION-1355
Live reload needs a watcher to learn when to push a reload, so it cannot work
in "off" mode. `ui5 serve` now resolves liveReload from all three sources
(default, --live-reload, server.settings.liveReload) and then, when watching is
disabled, forces it off regardless of how it was enabled.

With liveReload false the server skips minting the WebSocket token and calling
attachLiveReloadServer(), so no live-reload WebSocket server is attached and no
server-side change is needed. isWatchingDisabled() is imported dynamically
inside the handler, matching how the command already loads its other
@ui5/project and @ui5/server dependencies.

On startup in "off" mode the handler logs that file watching is disabled and
that resources still build on demand but changes are not picked up while the
server runs. When live reload was enabled, it additionally logs that live
reload has been disabled because it requires a watcher.
Add a Troubleshooting section pointing CI users at UI5_WATCH_MODE=off, next to
the existing polling/native guidance. It states what "off" removes (rebuilding
on change and live reload), what it keeps (building resources on demand), and
that sources and configuration must not change while the server runs, or it can
return an inconsistent result until restarted.

Keep the fileWatcher facade description in the incremental-build skill
reference in sync: the "off" mode, the inert subscription it returns, and that
the memoized decision is now a mode string behind shouldUsePolling() and
isWatchingDisabled().
@RandomByte
RandomByte force-pushed the feat/ui5-watch-mode-off branch from da7da79 to ed49fcc Compare September 24, 2026 13:08
@RandomByte
RandomByte marked this pull request as ready for review September 24, 2026 13:27
@RandomByte
RandomByte requested a review from a team September 24, 2026 13:27

@d3xter666 d3xter666 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The solution looks good and works as expected.

I would appreciate if someone can take another look, but apart the small comments in the tests, it LGTM

Comment on lines +605 to +607
t.is(server.serve.callCount, 1);
t.is(server.serve.getCall(0).args[1].liveReload, false,
"off wins over an explicit --live-reload");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, in that case it would be good also to ensure/test that live-reload disabling logs an info message.

Comment on lines +619 to +621
t.is(server.serve.callCount, 1);
t.is(server.serve.getCall(0).args[1].liveReload, false,
"off wins over server.settings.liveReload");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like my previous comment. To me it seems that we must test here whether a log message for the liveReload override is displayed

test.serial("subscribe: returns an inert subscription when UI5_WATCH_MODE=off", async (t) => {
// Disabled mode must not load any backend. The native stub stands in as a tripwire: if subscribe()
// delegated, it would be called. The returned subscription is real enough to track and unsubscribe.
process.env.UI5_WATCH_MODE = "off";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running this in an non isolated environment will set this to UI5_WATCH_MODE="off" and it might cause unexpected consequences.
Luckily, AVA creates subprocesses and we are kind of safe in that case, but it's worth pointing it.

Maybe it will be good if we set this for the sake of the test, but in the end to restore its original value, like we do here, for example: https://github.com/UI5/cli/blob/main/packages/cli/test/lib/dataDir.js#L27-L31

});

test.serial("isWatchingDisabled: true only for UI5_WATCH_MODE=off", async (t) => {
process.env.UI5_WATCH_MODE = "off";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@d3xter666
d3xter666 requested a review from a team September 25, 2026 13:45

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants