Skip to content

Commit 9fee705

Browse files
committed
feat(project): Add liveReload server setting and BuildServer event
- Add new `server.settings.liveReload` boolean option to the project configuration schema, available with specVersion 5.0 and higher. - BuildServer now emits a debounced `sourcesChanged` event (100ms debounce) whenever watched source files change, so a burst of changes results in a single notification. JIRA: CPOUI5FOUNDATION-1224
1 parent 19a6480 commit 9fee705

9 files changed

Lines changed: 238 additions & 4 deletions

File tree

internal/documentation/docs/pages/Configuration.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,7 @@ server:
650650
settings:
651651
httpPort: 1337
652652
httpsPort: 1443
653+
liveReload: true
653654
```
654655

655656
:::
@@ -662,6 +663,8 @@ A project can also configure alternative default ports. If the configured port i
662663

663664
The default and configured server ports can always be overwritten with the CLI parameter `--port`.
664665

666+
The `liveReload` setting controls whether the browser automatically reloads when project sources change. It defaults to `true` when running `ui5 serve` and can be overridden via the CLI parameter `--live-reload`. Requires [Specification Version](#specification-versions) 5.0 or higher.
667+
665668
## Extension Configuration
666669

667670
::: details Example
@@ -809,6 +812,10 @@ Version | UI5 CLI Release
809812

810813
### Specification Version 5.0
811814

815+
**Features:**
816+
817+
- Adds new server setting [`server.settings.liveReload`](#server-configuration) to control automatic browser reload on source changes
818+
812819
Specification Version 5.0 projects are supported by [UI5 CLI](https://github.com/UI5/cli) v5.0.0 and above.
813820

814821
### Specification Version 4.0

packages/project/lib/build/BuildServer.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import {SourceChangedDuringBuildError} from "./cache/ProjectBuildCache.js";
66
import {getLogger} from "@ui5/logger";
77
const log = getLogger("build:BuildServer");
88

9+
// Debounce window for the `sourcesChanged` event so a burst of file changes
10+
// results in a single notification.
11+
const SOURCES_CHANGED_DEBOUNCE_MS = 100;
12+
913
class AbortBuildError extends Error {
1014
constructor(message) {
1115
super(message);
@@ -44,6 +48,7 @@ class BuildServer extends EventEmitter {
4448
#pendingBuildRequest = new Set();
4549
#activeBuild = null;
4650
#processBuildRequestsTimeout;
51+
#sourcesChangedTimeout;
4752
#destroyed = false;
4853
#allReader;
4954
#rootReader;
@@ -154,6 +159,7 @@ class BuildServer extends EventEmitter {
154159
async destroy() {
155160
this.#destroyed = true;
156161
clearTimeout(this.#processBuildRequestsTimeout);
162+
clearTimeout(this.#sourcesChangedTimeout);
157163
await this.#watchHandler.destroy();
158164
try {
159165
if (this.#activeBuild) {
@@ -308,10 +314,14 @@ class BuildServer extends EventEmitter {
308314
this.#resourceChangeQueue.set(project.getName(), new Set([filePath]));
309315
}
310316

311-
// : Emit event debounced
312-
// Emit change event immediately so that consumers can react to it (like browser reloading)
313-
// const changedResourcePaths = [...changes.values()].flat();
314-
// this.emit("sourcesChanged", changedResourcePaths);
317+
// Debounced emit so a burst of file changes results in a single reload notification
318+
if (this.#sourcesChangedTimeout) {
319+
clearTimeout(this.#sourcesChangedTimeout);
320+
}
321+
this.#sourcesChangedTimeout = setTimeout(() => {
322+
this.#sourcesChangedTimeout = null;
323+
this.emit("sourcesChanged");
324+
}, SOURCES_CHANGED_DEBOUNCE_MS);
315325
}
316326

317327
#flushResourceChanges() {

packages/project/lib/validation/schema/specVersion/kind/project.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,33 @@
2424
}
2525
}
2626
}
27+
},
28+
{
29+
"if": {
30+
"type": "object",
31+
"properties": {
32+
"specVersion": {
33+
"not": {"const": "5.0"}
34+
}
35+
}
36+
},
37+
"then": {
38+
"type": "object",
39+
"properties": {
40+
"server": {
41+
"type": "object",
42+
"properties": {
43+
"settings": {
44+
"type": "object",
45+
"not": {
46+
"required": ["liveReload"]
47+
},
48+
"errorMessage": "The 'liveReload' setting is only supported with specVersion '5.0' and higher."
49+
}
50+
}
51+
}
52+
}
53+
}
2754
}
2855
],
2956
"properties": {
@@ -691,6 +718,9 @@
691718
},
692719
"httpsPort": {
693720
"type": "number"
721+
},
722+
"liveReload": {
723+
"type": "boolean"
694724
}
695725
}
696726
},

packages/project/test/lib/validation/schema/specVersion/kind/project.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,3 +285,47 @@ test("Legacy: Special characters in name (module)", async (t) => {
285285
}
286286
});
287287
});
288+
289+
test("server.settings.liveReload (specVersion 5.0)", async (t) => {
290+
await assertValidation(t, {
291+
"specVersion": "5.0",
292+
"type": "application",
293+
"metadata": {
294+
"name": "my-application"
295+
},
296+
"server": {
297+
"settings": {
298+
"liveReload": true
299+
}
300+
}
301+
});
302+
});
303+
304+
test("server.settings.liveReload (legacy specVersion)", async (t) => {
305+
await assertValidation(t, {
306+
"specVersion": "4.0",
307+
"type": "application",
308+
"metadata": {
309+
"name": "my-application"
310+
},
311+
"server": {
312+
"settings": {
313+
"liveReload": true
314+
}
315+
}
316+
}, [{
317+
instancePath: "/server/settings",
318+
keyword: "errorMessage",
319+
message: "The 'liveReload' setting is only supported with specVersion '5.0' and higher.",
320+
params: {
321+
errors: [{
322+
emUsed: true,
323+
instancePath: "/server/settings",
324+
keyword: "not",
325+
message: "must NOT be valid",
326+
params: {},
327+
schemaPath: "#/allOf/1/then/properties/server/properties/settings/not",
328+
}],
329+
}
330+
}]);
331+
});

packages/project/test/lib/validation/schema/specVersion/kind/project/application.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,38 @@ SpecificationVersion.getVersionsForRange(">=4.0").forEach(function(specVersion)
338338
}
339339
]);
340340
});
341+
342+
test(`Server liveReload setting (specVersion ${specVersion})`, async (t) => {
343+
const config = {
344+
"specVersion": specVersion,
345+
"type": "application",
346+
"metadata": {
347+
"name": "com.sap.ui5.test"
348+
},
349+
"server": {
350+
"settings": {
351+
"liveReload": true
352+
}
353+
}
354+
};
355+
if (new SpecificationVersion(specVersion).gte("5.0")) {
356+
await assertValidation(t, config);
357+
} else {
358+
await assertValidation(t, config, [{
359+
instancePath: "/server/settings",
360+
keyword: "errorMessage",
361+
message: "The 'liveReload' setting is only supported with specVersion '5.0' and higher.",
362+
params: {
363+
errors: [{
364+
instancePath: "/server/settings",
365+
keyword: "not",
366+
message: "must NOT be valid",
367+
params: {},
368+
}],
369+
}
370+
}]);
371+
}
372+
});
341373
});
342374

343375
SpecificationVersion.getVersionsForRange("2.0 - 3.2").forEach(function(specVersion) {

packages/project/test/lib/validation/schema/specVersion/kind/project/component.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,21 @@ SpecificationVersion.getVersionsForRange(">=5.0").forEach(function(specVersion)
960960
},
961961
}]);
962962
});
963+
964+
test(`Server liveReload setting (specVersion ${specVersion})`, async (t) => {
965+
await assertValidation(t, {
966+
"specVersion": specVersion,
967+
"type": "component",
968+
"metadata": {
969+
"name": "my.component"
970+
},
971+
"server": {
972+
"settings": {
973+
"liveReload": true
974+
}
975+
}
976+
});
977+
});
963978
});
964979

965980
project.defineTests(test, assertValidation, "component");

packages/project/test/lib/validation/schema/specVersion/kind/project/library.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,38 @@ SpecificationVersion.getVersionsForRange(">=4.0").forEach(function(specVersion)
265265
},
266266
]);
267267
});
268+
269+
test(`Server liveReload setting (specVersion ${specVersion})`, async (t) => {
270+
const config = {
271+
"specVersion": specVersion,
272+
"type": "library",
273+
"metadata": {
274+
"name": "my.library"
275+
},
276+
"server": {
277+
"settings": {
278+
"liveReload": true
279+
}
280+
}
281+
};
282+
if (new SpecificationVersion(specVersion).gte("5.0")) {
283+
await assertValidation(t, config);
284+
} else {
285+
await assertValidation(t, config, [{
286+
instancePath: "/server/settings",
287+
keyword: "errorMessage",
288+
message: "The 'liveReload' setting is only supported with specVersion '5.0' and higher.",
289+
params: {
290+
errors: [{
291+
instancePath: "/server/settings",
292+
keyword: "not",
293+
message: "must NOT be valid",
294+
params: {},
295+
}],
296+
}
297+
}]);
298+
}
299+
});
268300
});
269301

270302
SpecificationVersion.getVersionsForRange("2.0 - 3.2").forEach(function(specVersion) {

packages/project/test/lib/validation/schema/specVersion/kind/project/module.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,38 @@ SpecificationVersion.getVersionsForRange(">=2.5").forEach(function(specVersion)
174174
});
175175
});
176176

177+
test(`Server liveReload setting (specVersion ${specVersion})`, async (t) => {
178+
const config = {
179+
"specVersion": specVersion,
180+
"type": "module",
181+
"metadata": {
182+
"name": "my-module"
183+
},
184+
"server": {
185+
"settings": {
186+
"liveReload": true
187+
}
188+
}
189+
};
190+
if (new SpecificationVersion(specVersion).gte("5.0")) {
191+
await assertValidation(t, config);
192+
} else {
193+
await assertValidation(t, config, [{
194+
instancePath: "/server/settings",
195+
keyword: "errorMessage",
196+
message: "The 'liveReload' setting is only supported with specVersion '5.0' and higher.",
197+
params: {
198+
errors: [{
199+
instancePath: "/server/settings",
200+
keyword: "not",
201+
message: "must NOT be valid",
202+
params: {},
203+
}],
204+
}
205+
}]);
206+
}
207+
});
208+
177209
test(`module (specVersion ${specVersion}): builder/settings/includeDependency*`, async (t) => {
178210
await assertValidation(t, {
179211
"specVersion": specVersion,

packages/project/test/lib/validation/schema/specVersion/kind/project/theme-library.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,38 @@ SpecificationVersion.getVersionsForRange(">=2.0").forEach(function(specVersion)
167167
}
168168
}]);
169169
});
170+
171+
test(`Server liveReload setting (specVersion ${specVersion})`, async (t) => {
172+
const config = {
173+
"specVersion": specVersion,
174+
"type": "theme-library",
175+
"metadata": {
176+
"name": "my.theme.library"
177+
},
178+
"server": {
179+
"settings": {
180+
"liveReload": true
181+
}
182+
}
183+
};
184+
if (new SpecificationVersion(specVersion).gte("5.0")) {
185+
await assertValidation(t, config);
186+
} else {
187+
await assertValidation(t, config, [{
188+
instancePath: "/server/settings",
189+
keyword: "errorMessage",
190+
message: "The 'liveReload' setting is only supported with specVersion '5.0' and higher.",
191+
params: {
192+
errors: [{
193+
instancePath: "/server/settings",
194+
keyword: "not",
195+
message: "must NOT be valid",
196+
params: {},
197+
}],
198+
}
199+
}]);
200+
}
201+
});
170202
});
171203

172204
SpecificationVersion.getVersionsForRange(">=2.5").forEach(function(specVersion) {

0 commit comments

Comments
 (0)