@ui5/builder 5.0.0-alpha.2 → 5.0.0-alpha.4

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
@@ -4,6 +4,54 @@ This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html
4
4
 
5
5
  A list of unreleased changes can be found [here](https://github.com/SAP/ui5-builder/compare/v4.0.11...HEAD).
6
6
 
7
+ ## [5.0.0-alpha.4](https://github.com/UI5/cli/compare/builder-v5.0.0-alpha.3...builder-v5.0.0-alpha.4) (2026-03-24)
8
+
9
+
10
+ ### Features
11
+
12
+ * **builder:** Add dynamic section for API reference during JSDoc build ([47970f7](https://github.com/UI5/cli/commit/47970f74c4adf63625e4f330f19090083f563f82))
13
+ * **builder:** Add hierarchical subsection support for API reference sections ([49a3ef5](https://github.com/UI5/cli/commit/49a3ef569074ff84b5f6dfc2cfc619cd1654c2c8))
14
+ * **builder:** Automatically add [@lends](https://github.com/lends) tag to extend calls during JSdoc build ([3547f42](https://github.com/UI5/cli/commit/3547f425b33663f7e5e2618a6d876fe018058d99))
15
+ * **builder:** In JSDoc build, push the deprecation of namespaces down to the contained APIs ([71c08f2](https://github.com/UI5/cli/commit/71c08f2bb1d2cfee34ab9ca2e9e6042b5e67ae0c))
16
+ * **builder:** Mark cross-references to typedefs in the api.json (SDK variant) ([a474700](https://github.com/UI5/cli/commit/a47470050e804192bfab857f6c224faa6e445e10))
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * **builder:** Improve JSDoc support for "module:" syntax ([aca05f0](https://github.com/UI5/cli/commit/aca05f0b75561f33842ccb30248ede3a82f2ae46))
22
+ * **builder:** JSDoc: avoid errors when symbol has no events ([6e517e6](https://github.com/UI5/cli/commit/6e517e6c7f480784b93e37e94dcf74e0a689427f))
23
+
24
+
25
+ ### Dependencies
26
+
27
+ * The following workspace dependencies were updated
28
+ * dependencies
29
+ * @ui5/fs bumped from ^5.0.0-alpha.3 to ^5.0.0-alpha.4
30
+ * @ui5/logger bumped from ^5.0.0-alpha.3 to ^5.0.0-alpha.4
31
+ * devDependencies
32
+ * @ui5/project bumped from ^5.0.0-alpha.3 to ^5.0.0-alpha.4
33
+
34
+ ## [5.0.0-alpha.3](https://github.com/UI5/cli/compare/builder-v5.0.0-alpha.2...builder-v5.0.0-alpha.3) (2026-02-13)
35
+
36
+
37
+ ### Bug Fixes
38
+
39
+ * Change Markdown syntax to [@example](https://github.com/example) in one place ([f7e2e73](https://github.com/UI5/cli/commit/f7e2e732570e3948d87c200ed5d64f65891a866d))
40
+ * Fix gray code blocks in Markdown ([1cdd610](https://github.com/UI5/cli/commit/1cdd61060bd592b5c5f573635444d5ba37a8dc98))
41
+ * Remove unnecessary space ([892ebca](https://github.com/UI5/cli/commit/892ebcadbd9ac4a3c0db84c7fb12c8c1e5daa1b6))
42
+
43
+
44
+ ### Dependencies
45
+
46
+ * Bump escape-unicode from 0.2.0 to 0.3.0 ([#1160](https://github.com/UI5/cli/issues/1160)) ([90d07bd](https://github.com/UI5/cli/commit/90d07bdf9703e1072a8453a58302c00ac60ad83e))
47
+ * Bump workerpool from 9.3.4 to 10.0.1 ([#1171](https://github.com/UI5/cli/issues/1171)) ([1f659b4](https://github.com/UI5/cli/commit/1f659b405038f91f6aa62d0cc29c32376e4e24d3))
48
+ * The following workspace dependencies were updated
49
+ * dependencies
50
+ * @ui5/fs bumped from ^5.0.0-alpha.2 to ^5.0.0-alpha.3
51
+ * @ui5/logger bumped from ^5.0.0-alpha.2 to ^5.0.0-alpha.3
52
+ * devDependencies
53
+ * @ui5/project bumped from ^5.0.0-alpha.2 to ^5.0.0-alpha.3
54
+
7
55
  ## [5.0.0-alpha.2](https://github.com/UI5/cli/compare/builder-v5.0.0-alpha.1...builder-v5.0.0-alpha.2) (2025-12-15)
8
56
 
9
57
 
@@ -56,6 +56,7 @@ class BundleResolver {
56
56
  * in the resource pool.
57
57
  */
58
58
  const missingModules = Object.create(null);
59
+ const ignoreMissingModules = pool.getIgnoreMissingModules();
59
60
  /**
60
61
  * Names of modules that are included in non-decomposable bundles.
61
62
  * If they occur in the missingModules, then this is not an error.
@@ -123,7 +124,7 @@ class BundleResolver {
123
124
  done = pool.findResourceWithInfo(resourceName)
124
125
  .catch( (err) => {
125
126
  // if the caller provided an error message, log it
126
- if ( msg ) {
127
+ if ( msg && !ignoreMissingModules ) {
127
128
  missingModules[resourceName] ??= [];
128
129
  missingModules[resourceName].push(msg);
129
130
  }
@@ -84,9 +84,10 @@ class ModuleInfo {
84
84
  * global scope. If a module declares global variables, all of them will be exported
85
85
  * with additional code of the form:
86
86
  *
87
- * <pre>
88
- * this["name"] = name;
89
- * </pre>
87
+ * ```js
88
+ * this["name"] = name;
89
+ * ```
90
+ *
90
91
  *
91
92
  * To avoid this, developers can either add "name" to the <code>ignoreGlobals</code>
92
93
  * attribute of a raw-module or they can completely suppress embedding of the module
@@ -141,10 +141,11 @@ export default class ResourceFilterList {
141
141
  * and vice versa.
142
142
  *
143
143
  * Example:
144
- * <pre>
144
+ * ```
145
145
  * !sap/ui/core/
146
146
  * +sap/ui/core/utils/
147
- * </pre>
147
+ * ```
148
+ *
148
149
  * excludes everything from sap/ui/core, but includes everything from the subpackage sap/ui/core/utils/.
149
150
  *
150
151
  * Note that the filter operates on the full name of each resource. If a resource name
@@ -29,10 +29,11 @@
29
29
  *
30
30
  * Contains functions to consume tokens on an input string.
31
31
  *
32
- * @example
32
+ * ```js
33
33
  * sap.ui.require(["sap/base/util/JSTokenizer"], function(JSTokenizer){
34
34
  * JSTokenizer().parseJS("{test:'123'}"); // {test:'123'}
35
35
  * });
36
+ * ```
36
37
  *
37
38
  * @class sap/base/util/JSTokenizer
38
39
  * @since 1.58
@@ -57,7 +57,10 @@ const log = getLogger("builder:processors:bundlers:moduleBundler");
57
57
  * denote an arbitrary number of characters or folder names.
58
58
  * Excludes should be marked with a leading exclamation mark '!'. The order of filters is relevant; a later
59
59
  * exclusion overrides an earlier inclusion, and vice versa.
60
- * @example <caption>List of modules as glob patterns that should be in- or excluded</caption>
60
+ *
61
+ * <i>Example:</i>
62
+ *
63
+ * ```js
61
64
  * // Includes everything from "some/path/to/module/",
62
65
  * // but excludes the subfolder "some/path/to/module/to/be/excluded/"
63
66
  * const section = {
@@ -66,6 +69,7 @@ const log = getLogger("builder:processors:bundlers:moduleBundler");
66
69
  * "!some/path/to/module/to/be/excluded/"
67
70
  * ]
68
71
  * };
72
+ * ```
69
73
  *
70
74
  * @property {boolean} [resolve=false] Whether (transitive) dependencies of modules that match the given filters
71
75
  * should be resolved and added to the module set
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * Node script to create cross-library API index files for use in the UI5 SDKs.
3
3
  *
4
- * (c) Copyright 2025 SAP SE or an SAP affiliate company.
4
+ * (c) Copyright 2026 SAP SE or an SAP affiliate company.
5
5
  * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
6
6
  */
7
7
 
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Node script to preprocess api.json files for use in the UI5 SDKs.
3
3
  *
4
- * (c) Copyright 2025 SAP SE or an SAP affiliate company.
4
+ * (c) Copyright 2026 SAP SE or an SAP affiliate company.
5
5
  * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
6
6
  */
7
7
 
@@ -47,10 +47,10 @@ const typeParser = new TypeParser();
47
47
  * @param {string} sLibraryFile Path to the .library file of the library, used to extract further documentation information
48
48
  * @param {string|string[]} vDependencyAPIFiles Path of folder that contains api.json files of predecessor libs or
49
49
  * an array of paths of those files
50
- * @param {string} sFAQDir Path to the directory containing the sources for the FAQ section in APiRef
50
+ * @param {string} sSectionsDir Path to the directory containing the sources for the sections (FAQ, documentation, etc.) in the API Reference
51
51
  * @returns {Promise} A Promise that resolves after the transformation has been completed
52
52
  */
53
- function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles, sFAQDir, options) {
53
+ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles, sSectionsDir, options) {
54
54
  const fs = options && options.fs || require("fs");
55
55
  const returnOutputFiles = options && !!options.returnOutputFiles;
56
56
 
@@ -59,7 +59,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
59
59
  log.info(" output file: " + sOutputFile);
60
60
  log.info(" library file: " + sLibraryFile);
61
61
  log.info(" dependency dir: " + vDependencyAPIFiles);
62
- log.info(" FAQ src dir: " + sFAQDir);
62
+ log.info(" sections src dir: " + sSectionsDir);
63
63
  if (options && options.fs) {
64
64
  log.info("Using custom fs.");
65
65
  }
@@ -156,9 +156,10 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
156
156
  * UI5Types: ["sap.ui.core.Control"] // skip template if its value is "${0}" (default value)
157
157
  * }
158
158
  * @param {string} sComplexType
159
+ * @param {Object<string,object>} oAllDependentAPIs Map of entity names to their symbol
159
160
  * @returns {{template=: string, UI5Types=: string[]}}
160
161
  */
161
- function parseUI5Types(sComplexType) {
162
+ function parseUI5Types(sComplexType, oAllDependentAPIs) {
162
163
  let oParsed;
163
164
  try {
164
165
  oParsed = typeParser.parseSimpleTypes(sComplexType, isUI5Type);
@@ -176,6 +177,14 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
176
177
 
177
178
  if (oParsed.simpleTypes?.length) { // it can be empty if none of the included simple types satisfied the filter function
178
179
  result.UI5Types = oParsed.simpleTypes;
180
+
181
+ // check whether the type refers to a typedef and set a marker
182
+ if (result.template == null && result.UI5Types.length === 1) {
183
+ const symbol = oAllDependentAPIs[result.UI5Types[0]];
184
+ if (symbol?.kind === "typedef" && Array.isArray(symbol.properties)) {
185
+ result.refersToTypedef = true;
186
+ }
187
+ }
179
188
  }
180
189
 
181
190
  return result;
@@ -266,6 +275,8 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
266
275
  * @param {object} oChainObject chain object
267
276
  */
268
277
  const transformApiJson = function (oChainObject) {
278
+ const {oAllDependentAPIs} = oChainObject;
279
+
269
280
  // Function is a copy from: LibraryInfo.js => LibraryInfo.prototype._getActualComponent => "match" inline method
270
281
  function matchComponent(sModuleName, sPattern) {
271
282
  sModuleName = sModuleName.toLowerCase();
@@ -435,7 +446,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
435
446
  // Types
436
447
  oParameter.types = [];
437
448
  if (oParameter.type) {
438
- oParameter.typeInfo = parseUI5Types(oParameter.type);
449
+ oParameter.typeInfo = parseUI5Types(oParameter.type, oAllDependentAPIs);
439
450
  // Keep file size in check
440
451
  delete oParameter.type;
441
452
  }
@@ -520,7 +531,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
520
531
  // Type
521
532
  if (oSymbol.kind !== "enum") { // enum properties don't have an own type
522
533
  if (oProperty.type) {
523
- oProperty.typeInfo = parseUI5Types(oProperty.type);
534
+ oProperty.typeInfo = parseUI5Types(oProperty.type, oAllDependentAPIs);
524
535
  // Keep file size in check
525
536
  delete oProperty.type;
526
537
  }
@@ -560,7 +571,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
560
571
 
561
572
  // Type
562
573
  if (oProperty.type) {
563
- oProperty.typeInfo = parseUI5Types(oProperty.type);
574
+ oProperty.typeInfo = parseUI5Types(oProperty.type, oAllDependentAPIs);
564
575
  // Keep file size in check
565
576
  delete oProperty.type;
566
577
  }
@@ -657,7 +668,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
657
668
  Object.keys(aParams).forEach((sParam) => {
658
669
  const sSince = aParams[sParam].since;
659
670
  const oDeprecated = aParams[sParam].deprecated;
660
- const oEvtInSymbol = oSymbol.events.find((e) => e.name === oEvent.name);
671
+ const oEvtInSymbol = oSymbol.events?.find((e) => e.name === oEvent.name);
661
672
  const oParamInSymbol = oEvtInSymbol && oEvtInSymbol.parameters[0] &&
662
673
  oEvtInSymbol.parameters[0].parameterProperties &&
663
674
  oEvtInSymbol.parameters[0].parameterProperties.getParameters &&
@@ -687,7 +698,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
687
698
 
688
699
  // Link Enabled
689
700
  if (oSetting.type) {
690
- oSetting.typeInfo = parseUI5Types(oSetting.type);
701
+ oSetting.typeInfo = parseUI5Types(oSetting.type, oAllDependentAPIs);
691
702
  delete oSetting.type; // Keep file size in check
692
703
  }
693
704
 
@@ -756,7 +767,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
756
767
  oEvent.parameters.forEach((oParameter) => {
757
768
 
758
769
  if (oParameter.type) {
759
- oParameter.typeInfo = parseUI5Types(oParameter.type);
770
+ oParameter.typeInfo = parseUI5Types(oParameter.type, oAllDependentAPIs);
760
771
  delete oParameter.type; // Keep file size in check
761
772
  }
762
773
 
@@ -789,7 +800,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
789
800
  if (oSymbol.methods) {
790
801
 
791
802
  // Pre-process methods
792
- methods.buildMethodsModel(oSymbol.methods);
803
+ methods.buildMethodsModel(oSymbol.methods, oAllDependentAPIs);
793
804
 
794
805
  oSymbol.methods.forEach((oMethod) => {
795
806
 
@@ -950,6 +961,14 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
950
961
  }
951
962
 
952
963
  function loadDependencyLibraryFiles (oChainObject) {
964
+ const oAllDependentAPIs = oChainObject.oAllDependentAPIs = Object.create(null);
965
+ // add symbols from current library
966
+ if (oChainObject.fileData.symbols) {
967
+ for (const oSymbol of oChainObject.fileData.symbols) {
968
+ oAllDependentAPIs[oSymbol.name] = oSymbol;
969
+ }
970
+ }
971
+
953
972
  if (!oChainObject.aDependentLibraryFiles) {
954
973
  return oChainObject;
955
974
  }
@@ -978,6 +997,12 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
978
997
  // In this case we don't add it to the dependency list to skip double iteration.
979
998
  if (oData && oChainObject.fileData.library !== oData.library) {
980
999
  oDependentAPIs[oData.library] = oData.symbols;
1000
+
1001
+ if (Array.isArray(oData.symbols)) {
1002
+ for (const oSymbol of oData.symbols) {
1003
+ oAllDependentAPIs[oSymbol.name] = oSymbol;
1004
+ }
1005
+ }
981
1006
  }
982
1007
  });
983
1008
 
@@ -987,22 +1012,69 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
987
1012
  }
988
1013
 
989
1014
  /**
990
- * Check for existence of FAQ data
991
- * (FAQ data must be defined as *.md files in the <code>sFAQDir</code>)
1015
+ * Check for existence of sections data
1016
+ * (Sections data must be defined as *.md files in the <code>sSectionsDir</code>)
992
1017
  * and add a boolean flag in case it exists
993
1018
  *
994
1019
  * @param oChainObject chain object
995
1020
  */
996
- function addFlagsForFAQData(oChainObject) {
997
- if (!sFAQDir) {
1021
+ function addFlagsForSectionsData(oChainObject) {
1022
+ if (!sSectionsDir) {
998
1023
  return oChainObject;
999
1024
  }
1025
+
1000
1026
  const slibName = oChainObject.fileData.library;
1027
+
1001
1028
  oChainObject.fileData.symbols.forEach(function(symbol) {
1002
- const sfile = symbol.name.replace(slibName, "").replace(/[.]/g, "/") + ".md";
1003
- if (fs.existsSync(path.join(sFAQDir, sfile))) {
1004
- symbol.hasFAQ = true;
1029
+ const sComponentPath = symbol.name.replace(slibName, "").replace(/^[.]/, "").replace(/[.]/g, "/");
1030
+ if (!sComponentPath) {
1031
+ return;
1005
1032
  }
1033
+
1034
+ const componentDir = path.join(sSectionsDir, sComponentPath);
1035
+ if (fs.existsSync(componentDir)) {
1036
+ try {
1037
+ const dirContents = fs.readdirSync(componentDir, { withFileTypes: true });
1038
+
1039
+ // Separate files and directories in one pass
1040
+ const mdFiles = [];
1041
+ const dirNames = new Set();
1042
+ dirContents.forEach((dirent) => {
1043
+ if (dirent.isFile() && dirent.name.endsWith('.md')) {
1044
+ mdFiles.push(dirent.name.replace(/\.md$/, ''));
1045
+ } else if (dirent.isDirectory()) {
1046
+ dirNames.add(dirent.name);
1047
+ }
1048
+ });
1049
+
1050
+ // Build customSections array with subsection support
1051
+ const customSections = mdFiles.map(function(sectionName) {
1052
+ // Check if there's a matching directory for this section
1053
+ if (dirNames.has(sectionName)) {
1054
+ try {
1055
+ const subsectionFiles = fs.readdirSync(path.join(componentDir, sectionName), { withFileTypes: true })
1056
+ .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.md'))
1057
+ .map((dirent) => dirent.name.replace(/\.md$/, ''));
1058
+
1059
+ if (subsectionFiles.length > 0) {
1060
+ return { name: sectionName, hasSubsections: true, subsections: subsectionFiles };
1061
+ }
1062
+ } catch (error) {
1063
+ log.error('Error scanning subsection directory:', path.join(componentDir, sectionName), error);
1064
+ }
1065
+ }
1066
+ return sectionName;
1067
+ });
1068
+
1069
+ if (customSections.length > 0) {
1070
+ symbol.customSections = customSections;
1071
+ }
1072
+
1073
+ } catch (error) {
1074
+ log.error('Error scanning component sections directory:', componentDir, error);
1075
+ }
1076
+ }
1077
+
1006
1078
  });
1007
1079
  return oChainObject;
1008
1080
  }
@@ -2175,7 +2247,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2175
2247
  * Adjusts methods info so that it can be easily displayed in a table
2176
2248
  * @param aMethods - the methods array initially coming from the server
2177
2249
  */
2178
- buildMethodsModel: function (aMethods) {
2250
+ buildMethodsModel: function (aMethods, oAllDependentAPIs) {
2179
2251
  var fnExtractParameterProperties = function (oParameter, aParameters, iDepth, aPhoneName) {
2180
2252
  if (oParameter.parameterProperties) {
2181
2253
  Object.keys(oParameter.parameterProperties).forEach(function (sProperty) {
@@ -2185,7 +2257,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2185
2257
 
2186
2258
  // Handle types
2187
2259
  if (oProperty.type) {
2188
- oProperty.typeInfo = parseUI5Types(oProperty.type);
2260
+ oProperty.typeInfo = parseUI5Types(oProperty.type, oAllDependentAPIs);
2189
2261
  // Keep file size in check
2190
2262
  delete oProperty.type;
2191
2263
  }
@@ -2217,7 +2289,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2217
2289
  if (oMethod.parameters) {
2218
2290
  oMethod.parameters.forEach(function (oParameter) {
2219
2291
  if (oParameter.type) {
2220
- oParameter.typeInfo = parseUI5Types(oParameter.type);
2292
+ oParameter.typeInfo = parseUI5Types(oParameter.type, oAllDependentAPIs);
2221
2293
  // Keep file size in check
2222
2294
  delete oParameter.type;
2223
2295
  }
@@ -2238,7 +2310,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2238
2310
  // Handle return values
2239
2311
  if (oMethod.returnValue && oMethod.returnValue.type) {
2240
2312
  // Handle types
2241
- oMethod.returnValue.typeInfo = parseUI5Types(oMethod.returnValue.type);
2313
+ oMethod.returnValue.typeInfo = parseUI5Types(oMethod.returnValue.type, oAllDependentAPIs);
2242
2314
  }
2243
2315
 
2244
2316
  });
@@ -2375,6 +2447,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2375
2447
  inputFile: sInputFile,
2376
2448
  outputFile: sOutputFile,
2377
2449
  libraryFile: sLibraryFile,
2450
+ sectionsDir: sSectionsDir,
2378
2451
  aDependentLibraryFiles: Array.isArray(vDependencyAPIFiles) ? vDependencyAPIFiles : null
2379
2452
  };
2380
2453
 
@@ -2387,7 +2460,7 @@ function transformer(sInputFile, sOutputFile, sLibraryFile, vDependencyAPIFiles,
2387
2460
  .then(getAPIJSONPromise)
2388
2461
  .then(loadDependencyLibraryFiles)
2389
2462
  .then(transformApiJson)
2390
- .then(addFlagsForFAQData)
2463
+ .then(addFlagsForSectionsData)
2391
2464
  .then(createApiRefApiJson);
2392
2465
  return p;
2393
2466
 
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * JSDoc3 plugin for UI5 documentation generation.
3
3
  *
4
- * (c) Copyright 2025 SAP SE or an SAP affiliate company.
4
+ * (c) Copyright 2026 SAP SE or an SAP affiliate company.
5
5
  * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
6
6
  */
7
7
 
@@ -73,7 +73,8 @@ const path = require('jsdoc/path');
73
73
  const logger = require('jsdoc/util/logger');
74
74
  const pluginConfig = (env.conf && env.conf.templates && env.conf.templates.ui5) || env.opts.sapui5 || {};
75
75
  const escope = require("escope");
76
- const {isSemVer} = require("./template/utils/versionUtil");
76
+ const templatePath = path.getResourcePath(env.opts.template) || "./template";
77
+ const {isSemVer} = require(`${templatePath}/utils/versionUtil`);
77
78
 
78
79
  /* ---- logging ---- */
79
80
 
@@ -1220,11 +1221,13 @@ function collectClassInfo(extendCall, classDoclet) {
1220
1221
  if ( classDoclet && classDoclet.augments && classDoclet.augments.length === 1 ) {
1221
1222
  baseType = classDoclet.augments[0];
1222
1223
  }
1224
+ const jsdocClassName = classDoclet?.kind === "class" ? classDoclet.alias || classDoclet.longname : undefined;
1225
+
1223
1226
  if ( isMemberExpression(extendCall.callee) ) {
1224
1227
  const baseCandidate = getResolvedObjectName(extendCall.callee.object);
1225
1228
  if ( baseCandidate && baseType == null ) {
1226
1229
  baseType = baseCandidate;
1227
- } else if ( baseCandidate !== baseType ) {
1230
+ } else if ( baseCandidate !== toRuntimeMetadataName(baseType) ) {
1228
1231
  future(`documented base type '${baseType}' doesn't match technical base type '${baseCandidate}'`);
1229
1232
  }
1230
1233
  }
@@ -1283,6 +1286,32 @@ function collectClassInfo(extendCall, classDoclet) {
1283
1286
  }
1284
1287
 
1285
1288
  const classInfoNode = extendCall.arguments[1];
1289
+ if (classInfoNode?.type === Syntax.ObjectExpression && jsdocClassName && addLeadingCommentNode) {
1290
+ // Take care of the @lends comment only
1291
+ // - when the class info is an object literal (otherwise lends makes no sense)
1292
+ // - when a class name is found (it might differ from the runtime metadata name)
1293
+ // - when the entity is documented as a class (or else '.prototype' would be wrong)
1294
+ const classInfoComment = getLeadingCommentNode(classInfoNode);
1295
+ if (classInfoComment == null) {
1296
+ // If the class info object doesn't have a @lends tag, add it
1297
+ const raw = `/** @lends ${jsdocClassName}.prototype */`;
1298
+ info(`lends comment injected for class '${jsdocClassName}'`);
1299
+ addLeadingCommentNode(classInfoNode, {
1300
+ type: "CommentBlock",
1301
+ raw,
1302
+ value: raw.slice(2,-2),
1303
+ loc: classInfoNode.loc
1304
+ });
1305
+ } else {
1306
+ // Otherwise check whether the name matches the name of the described class
1307
+ const doclet = new Doclet(getRawComment(classInfoComment), {});
1308
+ // The name after the @lends is surprisingly stored in the 'alias' property
1309
+ if (doclet.alias !== `${jsdocClassName}.prototype`) {
1310
+ future(`lends comment '${doclet.alias}' doesn't match documented class name '${jsdocClassName}'`);
1311
+ }
1312
+ }
1313
+ }
1314
+
1286
1315
  const classInfoMap = createPropertyMap(classInfoNode);
1287
1316
  if ( classInfoMap && classInfoMap.metadata && classInfoMap.metadata.value.type !== Syntax.ObjectExpression ) {
1288
1317
  warning(`class metadata exists but can't be analyzed. It is not of type 'ObjectExpression', but a '${classInfoMap.metadata.value.type}'.`);
@@ -1772,7 +1801,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
1772
1801
  }
1773
1802
 
1774
1803
  function name(prefix, n, _static) {
1775
- return oClassInfo.name + rname(prefix, n, _static);
1804
+ return jsdocClassName + rname(prefix, n, _static);
1776
1805
  }
1777
1806
 
1778
1807
  /*
@@ -1884,6 +1913,18 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
1884
1913
  const p = m ? m.index : -1;
1885
1914
  const hasSettingsDocs = rawClassComment.indexOf("The supported settings are:") >= 0;
1886
1915
  const classAccess = doclet?.access ?? "public";
1916
+
1917
+ // If doclet exists and has an alias or longname, use that when building the fully qualified
1918
+ // names of methods or events.
1919
+ // Do this only when the doclet is of kind "class". Many TypeScript classes unfortunately
1920
+ // use a broken pattern with @namespace (needs to be deprecated in babel-plugin-transform-modules-ui5)
1921
+ const jsdocClassName =
1922
+ (doclet?.kind === "class" ? doclet.alias || doclet.longname : undefined) || oClassInfo.name;
1923
+ if (jsdocClassName !== oClassInfo.name) {
1924
+ info("jsdoc class name differs from runtime metadata name", jsdocClassName, oClassInfo.name);
1925
+ }
1926
+
1927
+ const displayClassName = oClassInfo.name;
1887
1928
  const visibility = createVisibilityTags(classAccess, doclet?.__ui5?.stakeholders);
1888
1929
  const thisClass = 'this'; // oClassInfo.name
1889
1930
 
@@ -2007,7 +2048,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2007
2048
  }
2008
2049
 
2009
2050
  newJSDoc([
2010
- "Returns a metadata object for class " + oClassInfo.name + ".",
2051
+ "Returns a metadata object for class " + displayClassName + ".",
2011
2052
  "",
2012
2053
  "@returns {sap.ui.base.Metadata} Metadata object describing this class",
2013
2054
  visibility,
@@ -2018,7 +2059,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2018
2059
 
2019
2060
  if ( !oClassInfo["final"] ) {
2020
2061
  newJSDoc([
2021
- "Creates a new subclass of class " + oClassInfo.name + " with name <code>sClassName</code>",
2062
+ "Creates a new subclass of class " + displayClassName + " with name <code>sClassName</code>",
2022
2063
  "and enriches it with the information contained in <code>oClassInfo</code>.",
2023
2064
  "",
2024
2065
  "<code>oClassInfo</code> might contain the same kind of information as described in {@link " + (oClassInfo.baseType ? oClassInfo.baseType + ".extend" : "sap.ui.base.Object.extend Object.extend") + "}.",
@@ -2304,7 +2345,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2304
2345
  "",
2305
2346
  info.enableEventBubbling ? "This event bubbles up the control hierarchy." : "",
2306
2347
  "",
2307
- "@name " + oClassInfo.name + "#" + n,
2348
+ "@name " + jsdocClassName + "#" + n,
2308
2349
  "@event",
2309
2350
  devStateTags,
2310
2351
  "@param {sap.ui.base.Event} oControlEvent",
@@ -2321,10 +2362,10 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2321
2362
  newJSDoc(lines);
2322
2363
 
2323
2364
  newJSDoc([
2324
- "Attaches event handler <code>fnFunction</code> to the " + link + " event of this <code>" + oClassInfo.name + "</code>.",
2365
+ "Attaches event handler <code>fnFunction</code> to the " + link + " event of this <code>" + displayClassName + "</code>.",
2325
2366
  "",
2326
2367
  "When called, the context of the event handler (its <code>this</code>) will be bound to <code>oListener</code> if specified, ",
2327
- "otherwise it will be bound to this <code>" + oClassInfo.name + "</code> itself.",
2368
+ "otherwise it will be bound to this <code>" + displayClassName + "</code> itself.",
2328
2369
  "",
2329
2370
  !newStyle && info.doc ? info.doc : "",
2330
2371
  "",
@@ -2333,7 +2374,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2333
2374
  "@param {function(sap.ui.base.Event):void}",
2334
2375
  " fnFunction The function to be called when the event occurs",
2335
2376
  "@param {object}",
2336
- " [oListener] Context object to call the event handler with. Defaults to this <code>" + oClassInfo.name + "</code> itself",
2377
+ " [oListener] Context object to call the event handler with. Defaults to this <code>" + displayClassName + "</code> itself",
2337
2378
  "",
2338
2379
  "@returns {" + thisClass + "} Reference to <code>this</code> in order to allow method chaining",
2339
2380
  devStateTags,
@@ -2342,7 +2383,7 @@ function createAutoDoc(oClassInfo, classComment, doclet, node, parser, filename,
2342
2383
  "@function"
2343
2384
  ]);
2344
2385
  newJSDoc([
2345
- "Detaches event handler <code>fnFunction</code> from the " + link + " event of this <code>" + oClassInfo.name + "</code>.",
2386
+ "Detaches event handler <code>fnFunction</code> from the " + link + " event of this <code>" + displayClassName + "</code>.",
2346
2387
  "",
2347
2388
  "The passed function and listener object must match the ones used for event registration.",
2348
2389
  "",
@@ -2439,6 +2480,7 @@ function toRuntimeMetadataName(longname) {
2439
2480
 
2440
2481
  let isDocComment;
2441
2482
  let getLeadingCommentNode;
2483
+ let addLeadingCommentNode;
2442
2484
 
2443
2485
  // JSDoc added the node type <code>Syntax.File</code> with the same change that activated Babylon
2444
2486
  // See https://github.com/jsdoc3/jsdoc/commit/ffec4a42291de6d68e6240f304b68d6abb82a869
@@ -2464,6 +2506,15 @@ if ( Syntax.File === 'File' ) {
2464
2506
  return undefined;
2465
2507
  };
2466
2508
 
2509
+ addLeadingCommentNode = function(node, comment) {
2510
+ node.leadingComments ??= [];
2511
+ let index = node.leadingComments.length;
2512
+ while (index > 0 && node.leadingComments[index - 1].type !== 'CommentBlock') {
2513
+ index--;
2514
+ }
2515
+ node.leadingComments.splice(index, 0, comment);
2516
+ }
2517
+
2467
2518
  } else {
2468
2519
 
2469
2520
  // JSDoc versions before 3.5.0
@@ -2498,6 +2549,9 @@ if ( Syntax.File === 'File' ) {
2498
2549
 
2499
2550
  return comment;
2500
2551
  };
2552
+
2553
+ // not implemented for espree (no automatic injection of @lends comments)
2554
+ addLeadingCommentNode = undefined;
2501
2555
  }
2502
2556
 
2503
2557
  //--- comment related functions that are independent from the JSdoc version
@@ -1,7 +1,7 @@
1
1
  /*
2
2
  * JSDoc3 template for UI5 documentation generation.
3
3
  *
4
- * (c) Copyright 2025 SAP SE or an SAP affiliate company.
4
+ * (c) Copyright 2026 SAP SE or an SAP affiliate company.
5
5
  * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
6
6
  */
7
7
 
@@ -231,6 +231,7 @@ function publish(symbolSet) {
231
231
 
232
232
  // now resolve relationships
233
233
  const aRootNamespaces = createNamespaceTree(allSymbols);
234
+ pushDownDeprecations(allSymbols);
234
235
  createInheritanceTree(allSymbols);
235
236
  collectMembers(allSymbols);
236
237
  mergeEventDocumentation(allSymbols);
@@ -374,6 +375,33 @@ function makeNamespace(memberof) {
374
375
  ]);
375
376
  }
376
377
 
378
+ function pushDownDeprecations() {
379
+
380
+ function pushDown(oSymbol, sInheritedDeprecation) {
381
+ if (oSymbol == null || !isFirstClassSymbol(oSymbol)) {
382
+ return;
383
+ }
384
+ const sOwnDeprecation = oSymbol.deprecated;
385
+ if (sOwnDeprecation == null && sInheritedDeprecation != null) {
386
+ oSymbol.deprecated = sInheritedDeprecation;
387
+ info(`mark ${oSymbol.longname} as deprecated (${sInheritedDeprecation})`);
388
+ } else if (sOwnDeprecation != null) {
389
+ const info = extractSince(sOwnDeprecation);
390
+ const sinceBecause = info.since ? `As of version ${info.since}, because`: `Because`;
391
+ sInheritedDeprecation = `${sinceBecause} it is part of the deprecated package ${oSymbol.longname}`;
392
+ }
393
+ oSymbol.__ui5.children?.forEach((oChild) => pushDown(oChild, sInheritedDeprecation));
394
+ }
395
+
396
+ // w/o a library name we can't start with the library namespace
397
+ if (!templateConf.uilib) {
398
+ return;
399
+ }
400
+
401
+ // start the push down at the library namespace to ensure locality
402
+ pushDown(lookup(templateConf.uilib));
403
+ }
404
+
377
405
  //---- inheritance hierarchy ----------------------------------------------------------------------------
378
406
 
379
407
  /*
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Node script to parse type strings.
3
3
  *
4
- * (c) Copyright 2025 SAP SE or an SAP affiliate company.
4
+ * (c) Copyright 2026 SAP SE or an SAP affiliate company.
5
5
  * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
6
6
  */
7
7
 
@@ -491,11 +491,18 @@ async function createManifest(
491
491
  }
492
492
  }
493
493
 
494
- return {
494
+ const libraryMetadata = {
495
495
  i18n: i18n(),
496
496
  css: css(),
497
497
  content: content()
498
498
  };
499
+
500
+ if (process.env.UI5_CLI_EXPERIMENTAL_BUNDLE_INFO_PRELOAD) {
501
+ // Add flag indicating that the (currently experimental) bundleVersion 2 preload bundling is used
502
+ libraryMetadata.bundleVersion = 2;
503
+ }
504
+
505
+ return libraryMetadata;
499
506
  }
500
507
 
501
508
  const sapUI5 = {
@@ -441,6 +441,12 @@ class ManifestEnhancer {
441
441
 
442
442
  if (this.manifest["sap.app"].type === "library") {
443
443
  await this.processSapUi5LibraryI18n();
444
+
445
+ if (process.env.UI5_CLI_EXPERIMENTAL_BUNDLE_INFO_PRELOAD) {
446
+ // Add flag indicating that the (currently experimental) bundleVersion 2 preload bundling is used
447
+ this.manifest["sap.ui5"].library ??= {};
448
+ this.manifest["sap.ui5"].library.bundleVersion = 2;
449
+ }
444
450
  } else {
445
451
  await Promise.all([
446
452
  this.processSapAppI18n(),
@@ -1,4 +1,4 @@
1
- import escapeUnicode from "escape-unicode";
1
+ import {escapeUnicode} from "escape-unicode";
2
2
 
3
3
  /**
4
4
  * @public
@@ -57,10 +57,10 @@ const escapeNonAscii = function(string) {
57
57
  /**
58
58
  * Escapes non ASCII characters with unicode escape sequences.
59
59
  *
60
- * @example
60
+ * ```js
61
61
  * const encoding = nonAsciiEscaper.getEncodingFromAlias("ISO-8859-1");
62
62
  * nonAsciiEscaper({resources, options: {encoding}});
63
- *
63
+ * ```
64
64
  *
65
65
  * @public
66
66
  * @function default
@@ -28,14 +28,14 @@ function getTimestamp() {
28
28
  * @typedef {Object<string, {lazy: boolean}>} ManifestLibraries
29
29
  *
30
30
  * sample:
31
- * <pre>
31
+ * ```json
32
32
  * {
33
33
  * "sap.chart": {
34
34
  * "lazy": true
35
35
  * },
36
36
  * "sap.f": { }
37
37
  * }
38
- * </pre>
38
+ * ```
39
39
  */
40
40
 
41
41
  /**
@@ -366,14 +366,16 @@ const processLibraryInfo = async (libraryInfo) => {
366
366
  * @param {string} parameters.options.rootProjectVersion Version of the root project
367
367
  * @param {module:@ui5/builder/processors/versionInfoGenerator~LibraryInfo[]} parameters.options.libraryInfos Array of
368
368
  * objects representing libraries,
369
- * e.g. <pre>
369
+ * e.g.
370
+ * ```json
370
371
  * {
371
372
  * name: "lib.x",
372
373
  * version: "1.0.0",
373
374
  * libraryManifest: @ui5/fs/Resource,
374
375
  * embeddedManifests: @ui5/fs/Resource[]
375
376
  * }
376
- * </pre>
377
+ * ```
378
+ *
377
379
  * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving with an array containing the versionInfo resource
378
380
  */
379
381
 
@@ -1,3 +1,4 @@
1
+ import semver from "semver";
1
2
  import {getLogger} from "@ui5/logger";
2
3
  const log = getLogger("builder:tasks:bundlers:generateLibraryPreload");
3
4
  import moduleBundler from "../../processors/bundlers/moduleBundler.js";
@@ -32,6 +33,32 @@ function getDefaultLibraryPreloadFilters(namespace, excludes) {
32
33
  return filters;
33
34
  }
34
35
 
36
+ function getExperimentalDefaultLibraryPreloadFilters(namespace, excludes) {
37
+ const filters = [
38
+ `${namespace}/library.js`,
39
+ `!${namespace}/**/*-preload.js`, // exclude all bundles
40
+ `!${namespace}/designtime/`,
41
+ `!${namespace}/**/*.designtime.js`,
42
+ `!${namespace}/**/*.support.js`
43
+ ];
44
+
45
+ if (Array.isArray(excludes)) {
46
+ const allFilterExcludes = negateFilters(excludes);
47
+ // Add configured excludes at the end of filter list
48
+ allFilterExcludes.forEach((filterExclude) => {
49
+ // Allow all excludes (!) and limit re-includes (+) to the library namespace
50
+ if (filterExclude.startsWith("!") || filterExclude.startsWith(`+${namespace}/`)) {
51
+ filters.push(filterExclude);
52
+ } else {
53
+ log.warn(`Configured preload exclude contains invalid re-include: !${filterExclude.substr(1)}. ` +
54
+ `Re-includes must start with the library's namespace ${namespace}`);
55
+ }
56
+ });
57
+ }
58
+
59
+ return filters;
60
+ }
61
+
35
62
  function getBundleDefinition(namespace, excludes) {
36
63
  // Note: This configuration is only used when no bundle definition in ui5.yaml exists (see "skipBundles" parameter)
37
64
 
@@ -106,6 +133,69 @@ function getBundleDefinition(namespace, excludes) {
106
133
  };
107
134
  }
108
135
 
136
+ function getBundleInfoPreloadDefinition(namespace, excludes, coreVersion) {
137
+ const sections = [{
138
+ mode: "preload",
139
+ filters: getExperimentalDefaultLibraryPreloadFilters(namespace, excludes),
140
+ resolve: true
141
+ },
142
+ {
143
+ mode: "bundleInfo",
144
+ name: `${namespace}/_library-content.js`,
145
+ filters: getDefaultLibraryPreloadFilters(namespace, excludes),
146
+ resolve: false,
147
+ resolveConditional: false,
148
+ renderer: true
149
+ }];
150
+
151
+ if (coreVersion) {
152
+ const parsedVersion = semver.parse(coreVersion);
153
+ let targetUi5CoreVersionMajor = parsedVersion.major;
154
+
155
+ // legacy-free versions include changes of the upcoming major version
156
+ // so we should treat them the same as the next major version
157
+ if (
158
+ parsedVersion.prerelease.includes("legacy-free") ||
159
+ parsedVersion.prerelease.includes("legacy-free-SNAPSHOT") // Maven snapshot version
160
+ ) {
161
+ targetUi5CoreVersionMajor += 1;
162
+ }
163
+ if (parsedVersion) {
164
+ if (targetUi5CoreVersionMajor >= 2) {
165
+ // Do not include manifest.json in UI5 2.x and higher to allow for loading it upfront for all libraries
166
+ sections.unshift({
167
+ mode: "provided",
168
+ filters: [
169
+ `${namespace}/manifest.json`,
170
+ ]
171
+ });
172
+ }
173
+ }
174
+ }
175
+
176
+ return {
177
+ name: `${namespace}/library-preload.js`,
178
+ sections,
179
+ };
180
+ }
181
+
182
+ function getContentBundleDefinition(namespace, excludes) {
183
+ return {
184
+ name: `${namespace}/_library-content.js`,
185
+ sections: [{
186
+ mode: "provided",
187
+ filters: getExperimentalDefaultLibraryPreloadFilters(namespace, excludes),
188
+ resolve: true
189
+ }, {
190
+ mode: "preload",
191
+ filters: getDefaultLibraryPreloadFilters(namespace, excludes),
192
+ resolve: false,
193
+ resolveConditional: false,
194
+ renderer: true
195
+ }]
196
+ };
197
+ }
198
+
109
199
  function getDesigntimeBundleDefinition(namespace) {
110
200
  return {
111
201
  name: `${namespace}/designtime/library-preload.designtime.js`,
@@ -258,6 +348,7 @@ export default async function({workspace, taskUtil, options: {skipBundles = [],
258
348
  }
259
349
  const coreVersion = taskUtil?.getProject("sap.ui.core")?.getVersion();
260
350
  const allowStringBundling = taskUtil?.getProject().getSpecVersion().lt("4.0");
351
+ const createBundleInfoPreload = !!process.env.UI5_CLI_EXPERIMENTAL_BUNDLE_INFO_PRELOAD;
261
352
  const execModuleBundlerIfNeeded = ({options, resources}) => {
262
353
  if (skipBundles.includes(options.bundleDefinition.name)) {
263
354
  log.verbose(`Skipping generation of bundle ${options.bundleDefinition.name}`);
@@ -271,7 +362,9 @@ export default async function({workspace, taskUtil, options: {skipBundles = [],
271
362
  return moduleBundler({options, resources});
272
363
  };
273
364
 
274
- return nonDbgWorkspace.byGlob("/**/*.{js,json,xml,html,properties,library,js.map}").then(async (resources) => {
365
+ return nonDbgWorkspace.byGlob(
366
+ "/resources/**/*.{js,json,xml,html,properties,library,js.map}"
367
+ ).then(async (resources) => {
275
368
  // Find all libraries and create a library-preload.js bundle
276
369
 
277
370
  let p = Promise.resolve();
@@ -299,8 +392,8 @@ export default async function({workspace, taskUtil, options: {skipBundles = [],
299
392
  return !taskUtil.getTag(resource, taskUtil.STANDARD_TAGS.HasDebugVariant);
300
393
  }
301
394
  });
302
- unoptimizedResources =
303
- await unoptimizedWorkspace.byGlob("/**/*.{js,json,xml,html,properties,library,js.map}");
395
+ unoptimizedResources = await unoptimizedWorkspace.byGlob(
396
+ "/resources/**/*.{js,json,xml,html,properties,library,js.map}");
304
397
 
305
398
  unoptimizedModuleNameMapping = createModuleNameMapping({
306
399
  resources: unoptimizedResources,
@@ -390,42 +483,117 @@ export default async function({workspace, taskUtil, options: {skipBundles = [],
390
483
  const libraryNamespaceMatch = libraryIndicatorPath.match(libraryNamespacePattern);
391
484
  if (libraryNamespaceMatch && libraryNamespaceMatch[1]) {
392
485
  const libraryNamespace = libraryNamespaceMatch[1];
393
- const results = await Promise.all([
394
- execModuleBundlerIfNeeded({
395
- options: {
396
- bundleDefinition: getBundleDefinition(libraryNamespace, excludes),
397
- bundleOptions: {
398
- optimize: true,
399
- ignoreMissingModules: true
400
- }
401
- },
402
- resources
403
- }),
404
- execModuleBundlerIfNeeded({
405
- options: {
406
- bundleDefinition: getDesigntimeBundleDefinition(libraryNamespace),
407
- bundleOptions: {
408
- optimize: true,
409
- ignoreMissingModules: true,
410
- skipIfEmpty: true
411
- }
412
- },
413
- resources
414
- }),
415
- execModuleBundlerIfNeeded({
416
- options: {
417
- bundleDefinition: getSupportFilesBundleDefinition(libraryNamespace),
418
- bundleOptions: {
419
- optimize: false,
420
- ignoreMissingModules: true,
421
- skipIfEmpty: true
422
- }
423
- // Note: Although the bundle uses optimize=false, there is
424
- // no moduleNameMapping needed, as support files are excluded from minification.
425
- },
426
- resources
427
- })
428
- ]);
486
+ let results;
487
+ if (!createBundleInfoPreload) {
488
+ // Regular bundling
489
+ results = await Promise.all([
490
+ execModuleBundlerIfNeeded({
491
+ options: {
492
+ bundleDefinition: getBundleDefinition(libraryNamespace, excludes),
493
+ bundleOptions: {
494
+ optimize: true,
495
+ ignoreMissingModules: true
496
+ }
497
+ },
498
+ resources
499
+ }),
500
+ execModuleBundlerIfNeeded({
501
+ options: {
502
+ bundleDefinition: getDesigntimeBundleDefinition(libraryNamespace),
503
+ bundleOptions: {
504
+ optimize: true,
505
+ ignoreMissingModules: true,
506
+ skipIfEmpty: true
507
+ }
508
+ },
509
+ resources
510
+ }),
511
+ execModuleBundlerIfNeeded({
512
+ options: {
513
+ bundleDefinition: getSupportFilesBundleDefinition(libraryNamespace),
514
+ bundleOptions: {
515
+ optimize: false,
516
+ ignoreMissingModules: true,
517
+ skipIfEmpty: true
518
+ }
519
+ // Note: Although the bundle uses optimize=false, there is
520
+ // no moduleNameMapping needed, as support files are excluded from minification.
521
+ },
522
+ resources
523
+ })
524
+ ]);
525
+ } else {
526
+ log.info(
527
+ `Using experimental bundling with bundle info preload ` +
528
+ `for library ${libraryNamespace} in project ${projectName}`);
529
+ log.info(`Detected sap.ui.core version is ${coreVersion || "unknown"}`);
530
+
531
+
532
+ if (skipBundles.includes(`${libraryNamespace}/library-preload.js`) &&
533
+ !skipBundles.includes(`${libraryNamespace}/_library-content.js`)) {
534
+ // If the standard preload bundle is skipped, ensure to also skip the content bundle,
535
+ // since they depend on each other
536
+ skipBundles.push(`${libraryNamespace}/_library-content.js`);
537
+ }
538
+
539
+ if (skipBundles.includes(`${libraryNamespace}/_library-content.js`) &&
540
+ !skipBundles.includes(`${libraryNamespace}/library-preload.js`)) {
541
+ // If the content bundle is skipped, the default preload bundle must be skipped as well
542
+ throw new Error(
543
+ `A custom bundle '${libraryNamespace}/_library-content.js' has been defined, ` +
544
+ `but it also requires a corresponding custom bundle definition for ` +
545
+ `'${libraryNamespace}/library-preload.js'`);
546
+ }
547
+
548
+ // Experimental bundling with bundle info preload
549
+ results = await Promise.all([
550
+ execModuleBundlerIfNeeded({
551
+ options: {
552
+ bundleDefinition:
553
+ getBundleInfoPreloadDefinition(libraryNamespace, excludes, coreVersion),
554
+ bundleOptions: {
555
+ optimize: true,
556
+ ignoreMissingModules: true
557
+ }
558
+ },
559
+ resources
560
+ }),
561
+ execModuleBundlerIfNeeded({
562
+ options: {
563
+ bundleDefinition: getContentBundleDefinition(libraryNamespace, excludes),
564
+ bundleOptions: {
565
+ optimize: true,
566
+ ignoreMissingModules: true
567
+ }
568
+ },
569
+ resources
570
+ }),
571
+ execModuleBundlerIfNeeded({
572
+ options: {
573
+ bundleDefinition: getDesigntimeBundleDefinition(libraryNamespace),
574
+ bundleOptions: {
575
+ optimize: true,
576
+ ignoreMissingModules: true,
577
+ skipIfEmpty: true
578
+ }
579
+ },
580
+ resources
581
+ }),
582
+ execModuleBundlerIfNeeded({
583
+ options: {
584
+ bundleDefinition: getSupportFilesBundleDefinition(libraryNamespace),
585
+ bundleOptions: {
586
+ optimize: false,
587
+ ignoreMissingModules: true,
588
+ skipIfEmpty: true
589
+ }
590
+ // Note: Although the bundle uses optimize=false, there is
591
+ // no moduleNameMapping needed, as support files are excluded from minification.
592
+ },
593
+ resources
594
+ })
595
+ ]);
596
+ }
429
597
  const bundles = Array.prototype.concat.apply([], results).filter(Boolean);
430
598
  return Promise.all(bundles.map(({bundle, sourceMap} = {}) => {
431
599
  if (bundle) {
@@ -55,7 +55,9 @@ function getCreatorOptions(projectName) {
55
55
  * Therefore it is also not supported in combination with self-contained build.
56
56
  * </p>
57
57
  *
58
- * @example <caption>sample resources.json</caption>
58
+ * <i>sample resources.json</i>
59
+ *
60
+ * ```js
59
61
  * const resourcesJson = {
60
62
  * "_version": "1.1.0",
61
63
  * "resources": [
@@ -95,6 +97,7 @@ function getCreatorOptions(projectName) {
95
97
  * }
96
98
  * ]
97
99
  * };
100
+ * ```
98
101
  *
99
102
  * @public
100
103
  * @function default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ui5/builder",
3
- "version": "5.0.0-alpha.2",
3
+ "version": "5.0.0-alpha.4",
4
4
  "description": "UI5 CLI - Builder",
5
5
  "author": {
6
6
  "name": "SAP SE",
@@ -33,39 +33,19 @@
33
33
  "npm": ">= 8"
34
34
  },
35
35
  "scripts": {
36
- "test": "npm run lint && npm run coverage && npm run depcheck",
36
+ "test": "npm run lint && npm run coverage",
37
37
  "lint": "eslint ./",
38
38
  "unit": "rimraf test/tmp && ava",
39
39
  "unit-verbose": "rimraf test/tmp && cross-env UI5_LOG_LVL=verbose ava --verbose --serial",
40
40
  "unit-watch": "rimraf test/tmp && ava --watch",
41
41
  "unit-inspect": "cross-env UI5_LOG_LVL=verbose ava debug --break",
42
- "coverage": "rimraf test/tmp && nyc ava --node-arguments=\"--experimental-loader=@istanbuljs/esm-loader-hook\"",
43
- "preversion": "npm test",
44
- "version": "git-chglog --sort semver --next-tag v$npm_package_version -o CHANGELOG.md v4.0.0.. && git add CHANGELOG.md",
45
- "prepublishOnly": "git push --follow-tags",
46
- "release-note": "git-chglog --sort semver -c .chglog/release-config.yml v$npm_package_version",
47
- "depcheck": "depcheck --ignores @ui5/builder,@istanbuljs/esm-loader-hook,catharsis,rimraf,jsdoc --parsers='**/*.js:es6,**/*.cjs:es6'"
42
+ "coverage": "rimraf test/tmp && nyc ava --node-arguments=\"--experimental-loader=@istanbuljs/esm-loader-hook\""
48
43
  },
49
44
  "files": [
50
45
  "CHANGELOG.md",
51
46
  "lib/**",
52
47
  "LICENSES/**"
53
48
  ],
54
- "ava": {
55
- "files": [
56
- "test/lib/**/*.js"
57
- ],
58
- "watchMode": {
59
- "ignoreChanges": [
60
- "test/tmp/**"
61
- ]
62
- },
63
- "nodeArguments": [
64
- "--loader=esmock",
65
- "--no-warnings"
66
- ],
67
- "workerThreads": false
68
- },
69
49
  "nyc": {
70
50
  "reporter": [
71
51
  "lcov",
@@ -111,10 +91,10 @@
111
91
  },
112
92
  "dependencies": {
113
93
  "@jridgewell/sourcemap-codec": "^1.5.5",
114
- "@ui5/fs": "^5.0.0-alpha.2",
115
- "@ui5/logger": "^5.0.0-alpha.2",
94
+ "@ui5/fs": "^5.0.0-alpha.4",
95
+ "@ui5/logger": "^5.0.0-alpha.4",
116
96
  "cheerio": "1.0.0",
117
- "escape-unicode": "^0.2.0",
97
+ "escape-unicode": "^0.3.0",
118
98
  "escope": "^4.0.0",
119
99
  "espree": "^10.4.0",
120
100
  "graceful-fs": "^4.2.11",
@@ -123,16 +103,15 @@
123
103
  "pretty-data": "^0.40.0",
124
104
  "semver": "^7.7.2",
125
105
  "terser": "^5.44.0",
126
- "workerpool": "^9.3.4",
106
+ "workerpool": "^10.0.1",
127
107
  "xml2js": "^0.6.2"
128
108
  },
129
109
  "devDependencies": {
130
110
  "@istanbuljs/esm-loader-hook": "^0.3.0",
131
111
  "@jridgewell/trace-mapping": "^0.3.31",
132
- "@ui5/project": "^5.0.0-alpha.2",
112
+ "@ui5/project": "^5.0.0-alpha.4",
133
113
  "ava": "^6.4.1",
134
- "cross-env": "^7.0.3",
135
- "depcheck": "^1.4.7",
114
+ "cross-env": "^10.1.0",
136
115
  "eslint": "^9.36.0",
137
116
  "esmock": "^2.7.3",
138
117
  "line-column": "^1.0.2",