Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions internal/documentation/docs/pages/Server.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ Please be aware of the following risks when using the server:
::: info Removed Middleware
The `serveThemes` middleware has been removed in UI5 CLI v5. Theme compilation is now handled by the `buildThemes` build task, which pre-compiles all theme CSS files. The resulting CSS files (including `library.css`, `library-RTL.css`, `library-parameters.json`, and CSS Variables resources) are served via the `serveResources` middleware, providing the same functionality with better performance through build-time compilation and caching.

Custom middleware previously referencing `serveThemes` via `beforeMiddleware` or `afterMiddleware` will continue to work with automatic remapping and a deprecation warning. See the [v5 migration guide](../updates/migrate-v5.md) for details.
The `testRunner` middleware has also been removed in UI5 CLI v5. The QUnit TestRunner (`testrunner.html`, `testrunner.css`, `TestRunner.js`) is now provided by the UI5 framework (`sap.ui.core`) and is served via the `serveResources` middleware.

Custom middleware previously referencing `serveThemes` or `testRunner` via `beforeMiddleware` or `afterMiddleware` will continue to work with automatic remapping and a deprecation warning. See the [v5 migration guide](../updates/migrate-v5.md) for details.
:::

All available standard middleware are listed below in the order of their execution.
Expand All @@ -43,7 +45,6 @@ A project can also add custom middleware to the server by using the [Custom Serv
| `liveReloadClient` | See chapter [liveReload](#livereload) |
| `discovery` | See chapter [discovery](#discovery) |
| `serveResources` | See chapter [serveResources](#serveresources) |
| `testRunner` | See chapter [testRunner](#testrunner) |
| `versionInfo` | See chapter [versionInfo](#versioninfo) |
| `nonReadRequests` | See chapter [nonReadRequests](#nonreadrequests) |
| `serveIndex` | See chapter [serveIndex](#serveindex) |
Expand Down Expand Up @@ -105,9 +106,6 @@ The following file content transformations are executed:
- Escaping non-ASCII characters in `.properties` translation files based on a project's [configuration](./Configuration.md#encoding-of-properties-files)
- Enhancing the `manifest.json` with supported locales determined by available `.properties` [translation files](./Builder.md#generation-of-supported-locales)

### testRunner
Serves a static version of the UI5 QUnit TestRunner at `/test-resources/sap/ui/qunit/testrunner.html`.

### versionInfo
Generates and serves the version info file `/resources/sap-ui-version.json`, which is required for several framework functionalities.

Expand Down
11 changes: 7 additions & 4 deletions internal/documentation/docs/updates/migrate-v5.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Or update your global install via: `npm i --global @ui5/cli@next`

- **@ui5/server: Live Reload is enabled by default for `ui5 serve`**

- **@ui5/server: Standard middleware `serveThemes` and `testRunner` have been removed**

## Node.js and npm Version Support

Expand Down Expand Up @@ -268,21 +269,23 @@ If your project uses a custom middleware that provides live reload functionality
The following middleware has been removed from the [standard middlewares list](../pages/Server.md#standard-middleware):

* `serveThemes` — The `buildThemes` build task now handles theme compilation (LESS to CSS). Because server sessions now also perform builds, this task runs during a server start instead of on demand during runtime. The resulting CSS files are served by the `serveResources` middleware. This change improves performance through build-time compilation and caching while maintaining the same functionality.
* `testRunner` — The UI5 QUnit TestRunner resources (`testrunner.html`, `testrunner.css`, `TestRunner.js`) are now provided by the UI5 framework (`sap.ui.core`) and served via the `serveResources` middleware. All supported UI5 releases ship the relevant testrunner resources, so the dedicated middleware is no longer needed.

**Backward Compatibility:**
If your project or any custom middleware references a removed middleware via `beforeMiddleware` or `afterMiddleware`, UI5 CLI will automatically remap the reference to the nearest remaining middleware and log a deprecation warning. Your custom middleware will still be executed in the expected order.
If your project or any custom middleware references a removed middleware via `beforeMiddleware` or `afterMiddleware`, UI5 CLI keeps a no-op placeholder in the middleware execution order at the original slot. The custom middleware is executed in the same position as before and a deprecation warning is logged.

**What Changed:**
- Theme CSS files (`library.css`, `library-RTL.css`, etc.) are now **pre-built**
- Files are served via `serveResources` instead of being compiled on demand
- The same CSS files are available at the same URLs as before
- Theme files are served via `serveResources` instead of being compiled on demand
- TestRunner resources are served via `serveResources` from the UI5 framework instead of being shipped with UI5 CLI

**Recommended Action:**
Update your `ui5.yaml` configuration to reference an existing middleware instead.

| Removed Middleware | Replacement Behavior | Recommended `afterMiddleware` |
| ------------------ | -------------------- | ----------------------------- |
| `serveThemes` | CSS files pre-built by `buildThemes` task and served via `serveResources` | `testRunner` |
| `serveThemes` | CSS files pre-built by `buildThemes` task and served via `serveResources` | `serveResources` |
| `testRunner` | TestRunner resources served via `serveResources` from the UI5 framework | `serveResources` |

## Learn More

Expand Down
5 changes: 0 additions & 5 deletions packages/server/REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,3 @@ precedence = "aggregate"
SPDX-FileCopyrightText = ["2010 Sencha Inc.", "2011 LearnBoost", "2011 TJ Holowaychuk", "2014-2015 Douglas Christopher Wilson"]
SPDX-License-Identifier = "MIT"

[[annotations]]
path = "lib/middleware/testRunner/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2026 SAP SE or an SAP affiliate company and OpenUI5 contributors"
SPDX-License-Identifier = "Apache-2.0"
7 changes: 0 additions & 7 deletions packages/server/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@ import eslintCommonConfig from "../../eslint.common.config.js";

export default [
...eslintCommonConfig, // Load common ESLint config
{
// Add project-specific ESLint config rules here
// in order to override common config
ignores: [
"lib/middleware/testRunner/",
]
},
{
// Live reload client script runs in the browser, not Node.js
files: ["lib/liveReload/client.js"],
Expand Down
94 changes: 55 additions & 39 deletions packages/server/lib/middleware/MiddlewareManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,6 @@ import {getLogger} from "@ui5/logger";
const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty);
const log = getLogger("server:MiddlewareManager");

/**
* Mapping of removed standard middleware names to their predecessor and successor
* in the original execution order. Used to remap custom middleware references
* to the nearest remaining middleware when a removed middleware is referenced.
*/
const LEGACY_MIDDLEWARE_MAPPING = {
serveThemes: {before: "testRunner", after: "versionInfo"}
};

/**
* @private
* @typedef {object} MiddlewareResources
Expand Down Expand Up @@ -49,6 +40,7 @@ class MiddlewareManager {

this.middleware = Object.create(null);
this.middlewareExecutionOrder = [];
this.legacyMiddlewarePlaceholders = new Set();
this.middlewareUtil = new MiddlewareUtil({graph, project: rootProject});
}

Expand All @@ -64,10 +56,15 @@ class MiddlewareManager {
await this.addStandardMiddleware();
await this.addCustomMiddleware();

return this.middlewareExecutionOrder.map((name) => {
const m = this.middleware[name];
app.use(m.mountPath, m.middleware);
});
// Strip legacy placeholders for removed standard middlewares — they only existed
// in middlewareExecutionOrder to preserve slots for custom beforeMiddleware /
// afterMiddleware references and are not backed by an actual middleware.
return this.middlewareExecutionOrder
.filter((name) => !this.legacyMiddlewarePlaceholders.has(name))
.map((name) => {
const m = this.middleware[name];
app.use(m.mountPath, m.middleware);
});
}

/**
Expand All @@ -86,7 +83,7 @@ class MiddlewareManager {
customMiddleware, wrapperCallback, mountPath = "/",
beforeMiddleware, afterMiddleware
} = {}) {
if (this.middleware[middlewareName]) {
if (this.#isMiddlewareNameKnown(middlewareName)) {
throw new Error(`A middleware with the name ${middlewareName} has already been added`);
}

Expand All @@ -107,8 +104,7 @@ class MiddlewareManager {
}

if (beforeMiddleware || afterMiddleware) {
let refMiddlewareName = beforeMiddleware || afterMiddleware;
const originalRefMiddlewareName = refMiddlewareName; // Store original before any remapping
const refMiddlewareName = beforeMiddleware || afterMiddleware;
let refMiddlewareIdx = this.middlewareExecutionOrder.indexOf(refMiddlewareName);

if (refMiddlewareName === "connectUi5Proxy") {
Expand All @@ -119,33 +115,23 @@ class MiddlewareManager {
`Please see the migration guide at https://ui5.github.io/cli/updates/migrate-v3/`);
}

// Handle legacy middleware with graceful fallback
const legacyMapping = LEGACY_MIDDLEWARE_MAPPING[refMiddlewareName];
if (legacyMapping) {
// Replace with the appropriate fallback based on reference type
refMiddlewareName = afterMiddleware ? legacyMapping.before : legacyMapping.after;

// Warn when a removed standard middleware is referenced. The reference still resolves
// because the removed middleware is kept as a placeholder in middlewareExecutionOrder
// (see #addLegacyMiddlewarePlaceholder), preserving the original execution slot.
if (this.legacyMiddlewarePlaceholders.has(refMiddlewareName)) {
log.warn(
`Standard middleware "${originalRefMiddlewareName}" has been removed. ` +
`Standard middleware "${refMiddlewareName}" has been removed. ` +
`Custom middleware "${middlewareName}" defined in project ` +
`"${this.middlewareUtil.getProject()}" references it and ` +
`is now placed ${afterMiddleware ? "after" : "before"} ` +
`"${refMiddlewareName}" instead. ` +
`"${this.middlewareUtil.getProject()}" still references it. ` +
`The custom middleware will be executed in the slot the removed middleware ` +
`originally occupied, but the reference should be updated. ` +
`For details, see the migration guide at ` +
`https://ui5.github.io/cli/next/updates/migrate-v5`);
}

refMiddlewareIdx = this.middlewareExecutionOrder.indexOf(refMiddlewareName);

if (refMiddlewareIdx === -1) {
// Provide clear error message, including remapping context if applicable
const errorMsg = legacyMapping ?
`Could not find fallback middleware "${refMiddlewareName}" ` +
`(mapped from removed middleware "${originalRefMiddlewareName}"), ` +
`referenced by custom middleware "${middlewareName}"` :
`Could not find middleware ${refMiddlewareName}, referenced by custom ` +
`middleware ${middlewareName}`;
throw new Error(errorMsg);
throw new Error(`Could not find middleware ${refMiddlewareName}, referenced by custom ` +
`middleware ${middlewareName}`);
}
if (afterMiddleware) {
// Insert after index of referenced middleware
Expand Down Expand Up @@ -272,7 +258,8 @@ class MiddlewareManager {
});
}
});
await this.addMiddleware("testRunner");
this.#addLegacyMiddlewarePlaceholder("testRunner");
this.#addLegacyMiddlewarePlaceholder("serveThemes");
await this.addMiddleware("versionInfo", {
mountPath: "/resources/sap-ui-version.json"
});
Expand Down Expand Up @@ -327,11 +314,11 @@ class MiddlewareManager {
}

let middlewareName = middlewareDef.name;
if (this.middleware[middlewareName]) {
if (this.#isMiddlewareNameKnown(middlewareName)) {
// Middleware is already known
// => add a suffix to allow for multiple configurations of the same middleware
let suffixCounter = 0;
while (this.middleware[middlewareName]) {
while (this.#isMiddlewareNameKnown(middlewareName)) {
suffixCounter++; // Start at 1
middlewareName = `${middlewareDef.name}--${suffixCounter}`;
}
Expand Down Expand Up @@ -363,6 +350,35 @@ class MiddlewareManager {
});
}
}

/**
* Inserts a no-op placeholder for a removed standard middleware into the execution order.
* Placeholders preserve the original slot so custom middlewares referencing the removed
* middleware via beforeMiddleware / afterMiddleware keep their intended position.
* They are stripped in applyMiddleware() before mounting on the express app.
*
* @private
* @param {string} middlewareName Name of the removed standard middleware
*/
#addLegacyMiddlewarePlaceholder(middlewareName) {
this.legacyMiddlewarePlaceholders.add(middlewareName);
this.middlewareExecutionOrder.push(middlewareName);
}

/**
* Returns whether the given middleware name is already known — either registered
* in <code>this.middleware</code> or occupying a legacy placeholder slot.
* Placeholder names are treated as known to avoid collisions in the execution order:
* a custom middleware named e.g. "testRunner" would otherwise clash with the
* placeholder inserted to preserve its original slot.
*
* @private
* @param {string} middlewareName Middleware name to check
* @returns {boolean}
*/
#isMiddlewareNameKnown(middlewareName) {
return !!(this.middleware[middlewareName] || this.legacyMiddlewarePlaceholders.has(middlewareName));
}
}

export default MiddlewareManager;
1 change: 0 additions & 1 deletion packages/server/lib/middleware/middlewareRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const middlewareInfos = {
serveIndex: {path: "./serveIndex.js"},
discovery: {path: "./discovery.js"},
versionInfo: {path: "./versionInfo.js"},
testRunner: {path: "./testRunner.js"},
nonReadRequests: {path: "./nonReadRequests.js"}
};

Expand Down
62 changes: 0 additions & 62 deletions packages/server/lib/middleware/testRunner.js

This file was deleted.

Loading
Loading