cds-plugin-ui5 0.12.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [0.13.0](https://github.com/ui5-community/ui5-ecosystem-showcase/compare/cds-plugin-ui5@0.12.1...cds-plugin-ui5@0.13.0) (2025-05-20)
7
+
8
+
9
+ ### Features
10
+
11
+ * **cds-plugin-ui5:** add lazy module loading ([#1216](https://github.com/ui5-community/ui5-ecosystem-showcase/issues/1216)) ([58c88cf](https://github.com/ui5-community/ui5-ecosystem-showcase/commit/58c88cfede10db2ac4aa59f8447edb02e12aa883))
12
+
13
+
14
+
15
+
16
+
6
17
  ## [0.12.1](https://github.com/ui5-community/ui5-ecosystem-showcase/compare/cds-plugin-ui5@0.12.0...cds-plugin-ui5@0.12.1) (2025-05-18)
7
18
 
8
19
 
package/README.md CHANGED
@@ -75,6 +75,15 @@ The configuration can also be injected with the environment variable `CDS_PLUGIN
75
75
  CDS_PLUGIN_UI5_MODULES="{ \"ui5-bookshop\": { \"mountPath\": \"/the-bookshop\" } }" cds-serve
76
76
  ```
77
77
 
78
+ #### Lazy Loading
79
+
80
+ The plugin supports lazy loading of UI5 applications which means that the UI5 middlewares will not be applied by default. The first time a UI5 application will be accessed in the CDS server triggers the load and apply of the UI5 middlewares. This feature is not active by default and needs to be activated with the environment variable `CDS_PLUGIN_UI5_LAZY_LOADING`.
81
+
82
+ ```sh
83
+ # enable lazy loading for UI5 applications
84
+ CDS_PLUGIN_UI5_LAZY_LOADING=true cds watch
85
+ ```
86
+
78
87
  #### Logger
79
88
 
80
89
  The `cds-plugin-ui5` uses the logger from CDS. By default, it adds coloring to the logs from CDS. This can be disabled in general by using the environment variable `NO_COLOR` for the logger overall or specifically for the `cds-plugin-ui5` by setting the environment variable `CDS_PLUGIN_UI5_NO_CUSTOM_LOGGER`.
@@ -155,6 +164,8 @@ module.exports = async ({ log, resources, options }) => {
155
164
 
156
165
  The returned app pages will be added to the welcome page within the respective mount path.
157
166
 
167
+ > :warning: The app pages cannot be retrieved and injected when using the lazy loading option.
168
+
158
169
  ## Hints
159
170
 
160
171
  This section includes hints for the usage of the `cds-plugin-ui5` with other tools.
package/cds-plugin.js CHANGED
@@ -171,6 +171,9 @@ if (!skip) {
171
171
 
172
172
  const links = [];
173
173
 
174
+ // is lazy loading enabled?
175
+ const isLazyLoadingEnabled = process.env["CDS_PLUGIN_UI5_LAZY_LOADING"] === "true";
176
+
174
177
  // log the version of the cds-plugin-ui5
175
178
  logVersion();
176
179
 
@@ -182,32 +185,33 @@ if (!skip) {
182
185
  LOG.info(`Mounting ${mountPath} to UI5 app ${modulePath} (id=${moduleId})${config[moduleId] ? ` using config=${JSON.stringify(config[moduleId])}` : ""}`);
183
186
 
184
187
  // create a patched router
185
- const router = await createPatchedRouter();
188
+ const router = createPatchedRouter();
186
189
 
187
- // apply the UI5 middlewares to the router and
188
- // retrieve the available HTML pages
189
- const appInfo = await applyUI5Middleware(router, {
190
+ // determine the application info and apply the UI5 middlewares (maybe lazy)
191
+ applyUI5Middleware(router, {
190
192
  cwd,
191
193
  basePath: modulePath,
192
194
  ...(config[moduleId] || {}),
193
195
  LOG,
196
+ lazy: isLazyLoadingEnabled,
197
+ //logPerformance: true,
198
+ }).then(({ pages }) => {
199
+ // append the HTML pages to the links
200
+ pages.forEach((page) => {
201
+ const prefix = mountPath !== "/" ? mountPath : "";
202
+ links.push(`${prefix}${page}`);
203
+ });
194
204
  });
195
205
 
196
206
  // register the router to the specified mount path
197
207
  app.use(mountPath, router);
198
-
199
- // append the HTML pages to the links
200
- appInfo.pages.forEach((page) => {
201
- const prefix = mountPath !== "/" ? mountPath : "";
202
- links.push(`${prefix}${page}`);
203
- });
204
208
  }
205
209
 
206
210
  // identify whether the welcome page should be rewritten
207
211
  let rewrite = links.length > 0;
208
212
 
209
213
  // rewrite the welcome page
210
- if (rewrite) {
214
+ if (rewrite || isLazyLoadingEnabled) {
211
215
  // register the custom middleware (similar like in @sap/cds/server.js)
212
216
  app.get("/", function appendLinksToIndex(req, res, next) {
213
217
  req._cds_plugin_ui5 = true; // marker for patched router to ignore
@@ -28,14 +28,14 @@ const fs = require("fs");
28
28
  * root directory to the given router.
29
29
  * @param {import("express").Router} router Express Router instance
30
30
  * @param {applyUI5MiddlewareOptions} options configuration options
31
- * @returns {UI5AppInfo} UI5 application information object
31
+ * @returns {Promise<UI5AppInfo>} UI5 application information object
32
32
  */
33
33
  module.exports = async function applyUI5Middleware(router, options) {
34
- const { graphFromPackageDependencies } = await import("@ui5/project/graph");
35
- const { createReaderCollection } = await import("@ui5/fs/resourceFactory");
34
+ const millis = Date.now();
36
35
 
37
36
  options.cwd = options.cwd || process.cwd();
38
37
  options.basePath = options.basePath || process.cwd();
38
+ options.lazy = options.lazy || false;
39
39
 
40
40
  const log = options.log || console;
41
41
 
@@ -44,6 +44,12 @@ module.exports = async function applyUI5Middleware(router, options) {
44
44
  const workspaceConfigPath = options.workspaceConfigPath || options.basePath;
45
45
  const workspaceConfigFile = options.workspaceConfigFile || "ui5-workspace.yaml";
46
46
 
47
+ const logPerformance = options.logPerformance || false;
48
+
49
+ const millisImport = logPerformance && Date.now();
50
+ const { graphFromPackageDependencies } = await import("@ui5/project/graph");
51
+ logPerformance && log.info(`[PERF] Import took ${Date.now() - millisImport}ms`);
52
+
47
53
  const determineConfigPath = function (configPath, configFile) {
48
54
  // ensure that the config path is absolute
49
55
  if (!path.isAbsolute(configPath)) {
@@ -66,6 +72,8 @@ module.exports = async function applyUI5Middleware(router, options) {
66
72
  return undefined;
67
73
  };
68
74
 
75
+ // determine the project graph from the given options
76
+ const millisGraph = logPerformance && Date.now();
69
77
  const graph = await graphFromPackageDependencies({
70
78
  cwd: options.basePath,
71
79
  rootConfigPath: determineConfigPath(configPath, configFile),
@@ -74,72 +82,110 @@ module.exports = async function applyUI5Middleware(router, options) {
74
82
  versionOverride: options.versionOverride,
75
83
  cacheMode: options.cacheMode,
76
84
  });
85
+ logPerformance && log.info(`[PERF] Graph took ${Date.now() - millisGraph}ms`);
77
86
 
87
+ const millisRoot = logPerformance && Date.now();
88
+ // detect the root project
78
89
  const rootProject = graph.getRoot();
79
90
 
80
- const readers = [];
81
- await graph.traverseBreadthFirst(async function ({ project: dep }) {
82
- if (dep.getName() === rootProject.getName()) {
83
- // Ignore root project
84
- return;
85
- }
86
- readers.push(dep.getReader({ style: "runtime" }));
87
- });
88
-
89
- const dependencies = createReaderCollection({
90
- name: `Dependency reader collection for project ${rootProject.getName()}`,
91
- readers,
92
- });
93
-
91
+ // create a reader for the root project
94
92
  const rootReader = rootProject.getReader({ style: "runtime" });
95
-
96
- // TODO change to ReaderCollection once duplicates are sorted out
97
- const combo = createReaderCollection({
98
- name: "server - prioritize workspace over dependencies",
99
- readers: [rootReader, dependencies],
100
- });
101
- const resources = {
102
- rootProject: rootReader,
103
- dependencies: dependencies,
104
- all: combo,
105
- };
106
-
107
- // TODO: rework ui5-server API and make public
108
- const { default: MiddlewareManager } = await import("@ui5/server/internal/MiddlewareManager");
109
- const middlewareManager = new MiddlewareManager({
110
- graph,
111
- rootProject,
112
- resources,
113
- options: {
114
- //sendSAPTargetCSP,
115
- //serveCSPReports,
116
- //simpleIndex: true
117
- },
118
- });
119
- await middlewareManager.applyMiddleware(router);
93
+ logPerformance && log.info(`[PERF] Root project took ${Date.now() - millisRoot}ms`);
120
94
 
121
95
  // for Fiori elements based applications we need to invalidate the view cache
96
+ // so we need to append the query parameter to the HTML files (sap-ui-xx-viewCache=false)
122
97
  const isFioriElementsBased = rootProject.getFrameworkDependencies().find((lib) => lib.name.startsWith("sap.fe"));
123
98
 
124
99
  // collect app pages from workspace (glob testing: https://globster.xyz/ and https://codepen.io/mrmlnc/pen/OXQjMe)
125
100
  // -> but exclude the HTML fragments from the list of app pages!
101
+ const millisPages = logPerformance && Date.now();
126
102
  const pages = (await rootReader.byGlob("**/!(*.fragment).{html,htm}")).map((resource) => `${resource.getPath()}${isFioriElementsBased ? "?sap-ui-xx-viewCache=false" : ""}`);
127
-
128
- // collect app pages from middlewares implementing the getAppPages
129
- middlewareManager.middlewareExecutionOrder?.map((name) => {
130
- const { middleware } = middlewareManager.middleware?.[name] || {};
131
- if (typeof middleware?.getAppPages === "function") {
132
- const customAppPages = middleware.getAppPages();
133
- if (Array.isArray(customAppPages)) {
134
- pages.push(...customAppPages);
135
- } else {
136
- if (customAppPages) {
137
- log.warn(`The middleware ${name} returns an unexpected value for "getAppPages". The value must be either undefined or string[]! Ignoring app pages from middleware!`);
103
+ logPerformance && log.info(`[PERF] Pages took ${Date.now() - millisPages}ms`);
104
+
105
+ // method to create the middleware manager and to apply the middlewares
106
+ // to the express router provided as a parameter to this function
107
+ const apply = async () => {
108
+ // find the relevant readers for the dependencies
109
+ const readers = [];
110
+ await graph.traverseBreadthFirst(async function ({ project: dep }) {
111
+ if (dep.getName() === rootProject.getName()) {
112
+ // Ignore root project
113
+ return;
114
+ }
115
+ readers.push(dep.getReader({ style: "runtime" }));
116
+ });
117
+
118
+ const { createReaderCollection } = await import("@ui5/fs/resourceFactory");
119
+
120
+ // create a reader collection for the dependencies
121
+ const dependencies = createReaderCollection({
122
+ name: `Dependency reader collection for project ${rootProject.getName()}`,
123
+ readers,
124
+ });
125
+
126
+ // TODO change to ReaderCollection once duplicates are sorted out
127
+ const combo = createReaderCollection({
128
+ name: "server - prioritize workspace over dependencies",
129
+ readers: [rootReader, dependencies],
130
+ });
131
+ const resources = {
132
+ rootProject: rootReader,
133
+ dependencies: dependencies,
134
+ all: combo,
135
+ };
136
+
137
+ // TODO: rework ui5-server API and make public
138
+ const { default: MiddlewareManager } = await import("@ui5/server/internal/MiddlewareManager");
139
+ const middlewareManager = new MiddlewareManager({
140
+ graph,
141
+ rootProject,
142
+ resources,
143
+ options: {
144
+ //sendSAPTargetCSP,
145
+ //serveCSPReports,
146
+ //simpleIndex: true
147
+ },
148
+ });
149
+ await middlewareManager.applyMiddleware(router);
150
+
151
+ // collect app pages from middlewares implementing the getAppPages
152
+ // which will only work if the middleware is executed synchronously
153
+ middlewareManager.middlewareExecutionOrder?.map((name) => {
154
+ const { middleware } = middlewareManager.middleware?.[name] || {};
155
+ if (typeof middleware?.getAppPages === "function") {
156
+ if (!options.lazy) {
157
+ const customAppPages = middleware.getAppPages();
158
+ if (Array.isArray(customAppPages)) {
159
+ pages.push(...customAppPages);
160
+ } else {
161
+ if (customAppPages) {
162
+ log.warn(`The middleware ${name} returns an unexpected value for "getAppPages". The value must be either undefined or string[]! Ignoring app pages from middleware!`);
163
+ }
164
+ }
165
+ } else {
166
+ log.warn(`The middleware ${name} returns a function for "getAppPages" but the lazy option is enabled. The function will not be executed!`);
138
167
  }
139
168
  }
140
- }
141
- });
169
+ });
170
+ };
171
+
172
+ // install a callback in the router to apply the middlewares
173
+ if (options.lazy) {
174
+ let isApplied = false;
175
+ router.use(async (req, res, next) => {
176
+ if (!isApplied) {
177
+ await apply();
178
+ isApplied = true;
179
+ }
180
+ next();
181
+ });
182
+ } else {
183
+ await apply();
184
+ }
185
+
186
+ logPerformance && log.info(`[PERF] applyUI5Middleware took ${Date.now() - millis}ms`);
142
187
 
188
+ // return the UI5 application information
143
189
  return {
144
190
  pages,
145
191
  };
@@ -6,10 +6,10 @@ const rewriteHTML = require("./rewriteHTML");
6
6
  * from urls and disabling the encoding
7
7
  * @returns {Router} patched router
8
8
  */
9
- module.exports = async function createPatchedRouter() {
9
+ module.exports = function createPatchedRouter() {
10
10
  // create the router and get rid of the mount path
11
11
  const router = new Router();
12
- router.use(function (req, res, next) {
12
+ router.use(async function (req, res, next) {
13
13
  // store the original request information
14
14
  const { url, originalUrl, baseUrl } = req;
15
15
  req["ui5-patched-router"] = req["ui5-patched-router"] || {
@@ -64,7 +64,7 @@ module.exports = async function createPatchedRouter() {
64
64
  });
65
65
  h1?.insertAdjacentHTML("afterbegin", `<a href="/">🏡</a> / `);
66
66
  }
67
- }
67
+ },
68
68
  );
69
69
  }
70
70
  // next one!
@@ -32,7 +32,7 @@ module.exports = async function findUI5Modules({ cwd, cds, skipLocalApps, skipDe
32
32
  try {
33
33
  modulesConfig = JSON.parse(process.env.CDS_PLUGIN_UI5_MODULES);
34
34
  log.info(`Using modules configuration from env`);
35
- } catch (err) {
35
+ } catch {
36
36
  modulesConfig = pkgJson.cds?.["cds-plugin-ui5"]?.modules;
37
37
  }
38
38
  if (modulesConfig) {
@@ -101,10 +101,10 @@ module.exports = async function findUI5Modules({ cwd, cds, skipLocalApps, skipDe
101
101
  paths: [cwd],
102
102
  });
103
103
  return true;
104
- } catch (e) {
104
+ } catch {
105
105
  return false;
106
106
  }
107
- })
107
+ }),
108
108
  );
109
109
  }
110
110
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cds-plugin-ui5",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "description": "A CDS server plugin to inject the middlewares of all related UI5 tooling based projects.",
5
5
  "author": "Peter Muessig",
6
6
  "license": "Apache-2.0",
@@ -26,5 +26,5 @@
26
26
  "@sap/cds": ">=6.8.2",
27
27
  "express": ">=4.18.2"
28
28
  },
29
- "gitHead": "35b0530739ca74a6f61cffab4e0927dd7d129c10"
29
+ "gitHead": "0eb73f5043c510e48377e943d27f817d46178e00"
30
30
  }