@ui5/builder 3.5.1 → 4.0.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 +39 -3
- package/CONTRIBUTING.md +1 -1
- package/README.md +6 -6
- package/jsdoc.json +7 -2
- package/lib/lbt/bundle/Builder.js +155 -113
- package/lib/lbt/bundle/ResolvedBundleDefinition.js +11 -17
- package/lib/processors/bundlers/moduleBundler.js +19 -8
- package/lib/processors/jsdoc/apiIndexGenerator.js +1 -1
- package/lib/processors/jsdoc/jsdocGenerator.js +4 -11
- package/lib/processors/jsdoc/lib/package.json +3 -0
- package/lib/processors/jsdoc/lib/ui5/template/{publish.cjs → publish.js} +1 -1
- package/lib/processors/jsdoc/sdkTransformer.js +1 -1
- package/lib/processors/manifestEnhancer.js +420 -0
- package/lib/processors/minifier.js +5 -1
- package/lib/tasks/buildThemes.js +4 -0
- package/lib/tasks/bundlers/generateBundle.js +11 -2
- package/lib/tasks/bundlers/generateComponentPreload.js +15 -8
- package/lib/tasks/bundlers/generateFlexChangesBundle.js +1 -2
- package/lib/tasks/bundlers/generateLibraryPreload.js +11 -8
- package/lib/tasks/bundlers/generateStandaloneAppBundle.js +40 -21
- package/lib/tasks/bundlers/utils/applyDefaultsToBundleDefinition.js +35 -0
- package/lib/tasks/enhanceManifest.js +32 -0
- package/lib/tasks/generateCachebusterInfo.js +1 -2
- package/lib/tasks/generateThemeDesignerResources.js +1 -2
- package/lib/tasks/generateVersionInfo.js +3 -1
- package/lib/tasks/taskRepository.js +2 -1
- package/lib/tasks/transformBootstrapHtml.js +2 -3
- package/package.json +20 -21
- /package/lib/processors/jsdoc/lib/{createIndexFiles.cjs → createIndexFiles.js} +0 -0
- /package/lib/processors/jsdoc/lib/{transformApiJson.cjs → transformApiJson.js} +0 -0
- /package/lib/processors/jsdoc/lib/ui5/{plugin.cjs → plugin.js} +0 -0
- /package/lib/processors/jsdoc/lib/ui5/template/utils/{versionUtil.cjs → versionUtil.js} +0 -0
|
@@ -33,10 +33,10 @@ const log = getLogger("builder:processors:bundlers:moduleBundler");
|
|
|
33
33
|
* the ui5loader is available.
|
|
34
34
|
* </li>
|
|
35
35
|
* <li>
|
|
36
|
-
* <code>require</code>: A
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
36
|
+
* <code>require</code>: A `require` section is transformed into a `sap.ui.require` call with all the dependencies
|
|
37
|
+
* resolved. This module comes with an `async` flag. When set to false, the modules
|
|
38
|
+
* are loaded using `sap.ui.requireSync` instead of `sap.ui.require`.
|
|
39
|
+
* **Note:** The `sap.ui.requireSync` API is not available in UI5 version 2.x.
|
|
40
40
|
* </li>
|
|
41
41
|
* <li>
|
|
42
42
|
* <code>bundleInfo</code>: A 'bundleInfo' section describes the content of another named bundle. This information
|
|
@@ -75,6 +75,11 @@ const log = getLogger("builder:processors:bundlers:moduleBundler");
|
|
|
75
75
|
* @property {boolean} [declareRawModules=false] Whether raw modules should be declared after jQuery.sap.global
|
|
76
76
|
* became available. With the usage of the ui5loader, this flag should be set to 'false'
|
|
77
77
|
* @property {boolean} [sort=true] Whether the modules should be sorted by their dependencies
|
|
78
|
+
* @property {boolean} [async=true] Whether the `require` section of the module should be loaded asynchronously.
|
|
79
|
+
* When set to true, the modules are loaded using a single `sap.ui.require` call instead of multiple
|
|
80
|
+
* `sap.ui.requireSync` calls.
|
|
81
|
+
* The latter API is not available in UI5 version 2.x.
|
|
82
|
+
* **Note:** This property is available only for `mode=require`.
|
|
78
83
|
*/
|
|
79
84
|
|
|
80
85
|
/* eslint-disable max-len */
|
|
@@ -101,7 +106,6 @@ const log = getLogger("builder:processors:bundlers:moduleBundler");
|
|
|
101
106
|
* with an optimization marker
|
|
102
107
|
* @property {boolean} [addTryCatchRestartWrapper=false] Whether to wrap bootable bundles with
|
|
103
108
|
* a try/catch to filter out "Restart" errors
|
|
104
|
-
* @property {boolean} [usePredefineCalls=false] If set to 'true', sap.ui.predefine is used for UI5 modules
|
|
105
109
|
* @property {number} [numberOfParts=1] The number of parts the module bundle should be splitted
|
|
106
110
|
* @property {boolean} [ignoreMissingModules=false] When searching for modules which are optional for further
|
|
107
111
|
* processing, do not throw in case they are missing
|
|
@@ -133,26 +137,33 @@ const log = getLogger("builder:processors:bundlers:moduleBundler");
|
|
|
133
137
|
bundle definition
|
|
134
138
|
* @param {module:@ui5/builder/processors/bundlers/moduleBundler~ModuleBundleOptions} [parameters.options.bundleOptions] Module
|
|
135
139
|
bundle options
|
|
140
|
+
* @param {string} [parameters.options.targetUi5CoreVersion] Optional semver compliant sap.ui.core project version, e.g '2.0.0'.
|
|
141
|
+
This allows the bundler to make assumptions on available runtime APIs.
|
|
142
|
+
Omit if the ultimate UI5 version at runtime is unknown or can't be determined.
|
|
143
|
+
* @param {boolean} [parameters.options.allowStringBundling=false] Optional flag to allow bundling of modules as a string.
|
|
136
144
|
* @returns {Promise<module:@ui5/builder/processors/bundlers/moduleBundler~ModuleBundlerResult[]>}
|
|
137
145
|
* Promise resolving with module bundle resources
|
|
138
146
|
*/
|
|
139
147
|
/* eslint-enable max-len */
|
|
140
|
-
export default function({resources, options: {
|
|
148
|
+
export default function({resources, options: {
|
|
149
|
+
bundleDefinition, bundleOptions, moduleNameMapping, targetUi5CoreVersion, allowStringBundling = false
|
|
150
|
+
}}) {
|
|
141
151
|
// Apply defaults without modifying the passed object
|
|
142
152
|
bundleOptions = Object.assign({}, {
|
|
143
153
|
optimize: true,
|
|
144
154
|
sourceMap: true,
|
|
145
155
|
decorateBootstrapModule: false,
|
|
146
156
|
addTryCatchRestartWrapper: false,
|
|
147
|
-
usePredefineCalls: false,
|
|
148
157
|
numberOfParts: 1,
|
|
149
158
|
ignoreMissingModules: false
|
|
150
159
|
}, bundleOptions);
|
|
151
160
|
|
|
161
|
+
// bundleDefinition's defaults get applied in the corresponding standard tasks
|
|
162
|
+
|
|
152
163
|
const pool = new LocatorResourcePool({
|
|
153
164
|
ignoreMissingModules: bundleOptions.ignoreMissingModules
|
|
154
165
|
});
|
|
155
|
-
const builder = new BundleBuilder(pool);
|
|
166
|
+
const builder = new BundleBuilder(pool, targetUi5CoreVersion, allowStringBundling);
|
|
156
167
|
|
|
157
168
|
if (log.isLevelEnabled("verbose")) {
|
|
158
169
|
log.verbose(`Generating bundle:`);
|
|
@@ -4,12 +4,8 @@ import path from "node:path";
|
|
|
4
4
|
import {promisify} from "node:util";
|
|
5
5
|
const writeFile = promisify(fs.writeFile);
|
|
6
6
|
import {createAdapter} from "@ui5/fs/resourceFactory";
|
|
7
|
-
import {createRequire} from "node:module";
|
|
8
7
|
import {fileURLToPath} from "node:url";
|
|
9
8
|
|
|
10
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
const require = createRequire(import.meta.url);
|
|
12
|
-
|
|
13
9
|
/**
|
|
14
10
|
* @public
|
|
15
11
|
* @module @ui5/builder/processors/jsdoc/jsdocGenerator
|
|
@@ -93,12 +89,9 @@ async function generateJsdocConfig({targetPath, tmpPath, namespace, projectName,
|
|
|
93
89
|
const backslashRegex = /\\/g;
|
|
94
90
|
|
|
95
91
|
// Resolve path to this script to get the path to the JSDoc extensions folder
|
|
96
|
-
const jsdocPath = path.normalize(
|
|
97
|
-
const pluginPath = path.join(jsdocPath, "lib", "ui5", "plugin.
|
|
98
|
-
|
|
99
|
-
// jsdoc appends /publish to the provided path but doesn't allow to
|
|
100
|
-
// add the .cjs extension, so loading won't work otherwise.
|
|
101
|
-
const templatePath = "@ui5/builder/internal/jsdoc/template";
|
|
92
|
+
const jsdocPath = path.normalize(import.meta.dirname);
|
|
93
|
+
const pluginPath = path.join(jsdocPath, "lib", "ui5", "plugin.js").replace(backslashRegex, "\\\\");
|
|
94
|
+
const templatePath = path.join(jsdocPath, "lib", "ui5", "template").replace(backslashRegex, "\\\\");
|
|
102
95
|
const destinationPath = path.normalize(tmpPath).replace(backslashRegex, "\\\\");
|
|
103
96
|
const jsapiFilePath = path.join(targetPath, "libraries", projectName + ".js").replace(backslashRegex, "\\\\");
|
|
104
97
|
const apiJsonFolderPath = path.join(tmpPath, "dependency-apis").replace(backslashRegex, "\\\\");
|
|
@@ -164,7 +157,7 @@ async function writeJsdocConfig(targetDirPath, config) {
|
|
|
164
157
|
*/
|
|
165
158
|
async function buildJsdoc({sourcePath, configPath}) {
|
|
166
159
|
const args = [
|
|
167
|
-
|
|
160
|
+
fileURLToPath(import.meta.resolve("jsdoc/jsdoc.js")),
|
|
168
161
|
"-c",
|
|
169
162
|
configPath,
|
|
170
163
|
"--verbose",
|
|
@@ -26,7 +26,7 @@ const info = logger.info.bind(logger);
|
|
|
26
26
|
const warning = logger.warn.bind(logger);
|
|
27
27
|
const error = logger.error.bind(logger);
|
|
28
28
|
|
|
29
|
-
const {extractVersion, extractSince} = require("./utils/versionUtil
|
|
29
|
+
const {extractVersion, extractSince} = require("./utils/versionUtil");
|
|
30
30
|
|
|
31
31
|
/* errors that might fail the build in future */
|
|
32
32
|
function future(msg) {
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import semver from "semver";
|
|
2
|
+
const {SemVer: Version, lt} = semver;
|
|
3
|
+
import {promisify} from "node:util";
|
|
4
|
+
import path from "node:path/posix";
|
|
5
|
+
import {getLogger} from "@ui5/logger";
|
|
6
|
+
const log = getLogger("builder:processors:manifestEnhancer");
|
|
7
|
+
|
|
8
|
+
const APP_DESCRIPTOR_V22 = new Version("1.21.0");
|
|
9
|
+
|
|
10
|
+
function isAbsoluteUrl(url) {
|
|
11
|
+
if (url.startsWith("/")) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
const parsedUrl = new URL(url);
|
|
16
|
+
// URL with ui5 protocol shouldn't be treated as absolute URL and will be handled separately
|
|
17
|
+
return parsedUrl.protocol !== "ui5:";
|
|
18
|
+
} catch (err) {
|
|
19
|
+
// URL constructor without base requires absolute URL and throws an error for relative URLs
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Returns a bundle URL from the given bundle name, relative to the given namespace.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} bundleName Bundle name (e.g. "sap.ui.demo.app.i18n.i18n") to be resolved to a relative URL
|
|
28
|
+
* @param {string} sapAppId Project namespace from sap.app/id (e.g. "sap.ui.demo.app")
|
|
29
|
+
* to which a bundleName should be resolved to
|
|
30
|
+
* @returns {string} Relative bundle URL (e.g. "i18n/i18n.properties")
|
|
31
|
+
*/
|
|
32
|
+
function getRelativeBundleUrlFromName(bundleName, sapAppId) {
|
|
33
|
+
const bundleUrl = "/resources/" + bundleName.replace(/\./g, "/") + ".properties";
|
|
34
|
+
return normalizeBundleUrl(bundleUrl, sapAppId);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Copied from sap/base/util/LoaderExtensions.resolveUI5Url
|
|
38
|
+
// Adjusted to not resolve the URL, but create an absolute path prefixed with /resources
|
|
39
|
+
function resolveUI5Url(sUrl) {
|
|
40
|
+
// check for ui5 scheme
|
|
41
|
+
if (sUrl.startsWith("ui5:")) {
|
|
42
|
+
let sNoScheme = sUrl.replace("ui5:", "");
|
|
43
|
+
|
|
44
|
+
// check for authority
|
|
45
|
+
if (!sNoScheme.startsWith("//")) {
|
|
46
|
+
// URLs using the 'ui5' protocol must be absolute.
|
|
47
|
+
// Relative and server absolute URLs are reserved for future use.
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
sNoScheme = sNoScheme.replace("//", "");
|
|
52
|
+
|
|
53
|
+
return "/resources/" + sNoScheme;
|
|
54
|
+
} else {
|
|
55
|
+
// not a ui5 url
|
|
56
|
+
return sUrl;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Normalizes a bundle URL relative to the project namespace.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} bundleUrl Relative bundle URL to be normalized
|
|
64
|
+
* @param {string} sapAppId Project namespace from sap.app/id (e.g. "sap.ui.demo.app")
|
|
65
|
+
* to which the URL is relative to
|
|
66
|
+
* @returns {string} Normalized relative bundle URL (e.g. "i18n/i18n.properties")
|
|
67
|
+
*/
|
|
68
|
+
function normalizeBundleUrl(bundleUrl, sapAppId) {
|
|
69
|
+
// Create absolute path with namespace from sap.app/id
|
|
70
|
+
const absoluteNamespace = `/resources/${sapAppId.replaceAll(/\./g, "/")}`;
|
|
71
|
+
|
|
72
|
+
const resolvedAbsolutePath = path.resolve(absoluteNamespace, bundleUrl);
|
|
73
|
+
const resolvedRelativePath = path.relative(absoluteNamespace, resolvedAbsolutePath);
|
|
74
|
+
return resolvedRelativePath;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Returns the bundle URL from the given bundle configuration.
|
|
79
|
+
*
|
|
80
|
+
* @param {object} bundleConfig Bundle configuration
|
|
81
|
+
* @param {string} sapAppId Project namespace from sap.app/id (e.g. "sap.ui.demo.app")
|
|
82
|
+
* to which a bundleName should be resolved to
|
|
83
|
+
* @param {string} [defaultBundleUrl] Default bundle url in case bundleConfig is not defined
|
|
84
|
+
*/
|
|
85
|
+
function getBundleUrlFromConfig(bundleConfig, sapAppId, defaultBundleUrl) {
|
|
86
|
+
if (!bundleConfig) {
|
|
87
|
+
// Use default URL (or undefined if argument is not provided)
|
|
88
|
+
return defaultBundleUrl;
|
|
89
|
+
} else if (typeof bundleConfig === "string") {
|
|
90
|
+
return bundleConfig;
|
|
91
|
+
} else if (typeof bundleConfig === "object") {
|
|
92
|
+
return getBundleUrlFromConfigObject(bundleConfig, sapAppId);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Same as above, but does only accept objects, not strings or defaults
|
|
97
|
+
function getBundleUrlFromConfigObject(bundleConfig, sapAppId, fallbackBundleUrl) {
|
|
98
|
+
if (typeof bundleConfig === "object") {
|
|
99
|
+
if (bundleConfig.bundleName) {
|
|
100
|
+
return getRelativeBundleUrlFromName(bundleConfig.bundleName, sapAppId);
|
|
101
|
+
} else if (bundleConfig.bundleUrl) {
|
|
102
|
+
return bundleConfig.bundleUrl;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return fallbackBundleUrl;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// See runtime logic in sap/ui/core/Lib#_normalizeI18nSettings
|
|
109
|
+
function getBundleUrlFromSapUi5LibraryI18n(vI18n) {
|
|
110
|
+
if (vI18n == null || vI18n === true) {
|
|
111
|
+
return "messagebundle.properties";
|
|
112
|
+
} else if (typeof vI18n === "string") {
|
|
113
|
+
return vI18n;
|
|
114
|
+
} else if (typeof vI18n === "object") {
|
|
115
|
+
return vI18n.bundleUrl;
|
|
116
|
+
} else {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
class ManifestEnhancer {
|
|
122
|
+
/**
|
|
123
|
+
* @param {string} manifest manifest.json content
|
|
124
|
+
* @param {string} filePath manifest.json file path
|
|
125
|
+
* @param {fs} fs Node fs or custom [fs interface]{@link module:@ui5/fs/fsInterface}
|
|
126
|
+
*/
|
|
127
|
+
constructor(manifest, filePath, fs) {
|
|
128
|
+
this.fsReadDir = promisify(fs.readdir);
|
|
129
|
+
this.cwd = path.dirname(filePath);
|
|
130
|
+
this.filePath = filePath;
|
|
131
|
+
this.manifest = JSON.parse(manifest);
|
|
132
|
+
|
|
133
|
+
this.isModified = false;
|
|
134
|
+
this.runInvoked = false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
markModified() {
|
|
138
|
+
this.isModified = true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async readdir(relativePath) {
|
|
142
|
+
const absolutePath = path.resolve(this.cwd, relativePath);
|
|
143
|
+
try {
|
|
144
|
+
return await this.fsReadDir(absolutePath);
|
|
145
|
+
} catch (err) {
|
|
146
|
+
if (err?.code === "ENOENT") {
|
|
147
|
+
return [];
|
|
148
|
+
} else {
|
|
149
|
+
throw err;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async findSupportedLocales(i18nBundleUrl) {
|
|
155
|
+
const i18nBundleName = path.basename(i18nBundleUrl, ".properties");
|
|
156
|
+
const i18nBundlePrefix = `${i18nBundleName}_`;
|
|
157
|
+
const i18nBundleDir = path.dirname(i18nBundleUrl);
|
|
158
|
+
const i18nBundleFiles = await this.readdir(i18nBundleDir);
|
|
159
|
+
const supportedLocales = [];
|
|
160
|
+
i18nBundleFiles.forEach((fileName) => {
|
|
161
|
+
if (!fileName.endsWith(".properties")) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const fileNameWithoutExtension = path.basename(fileName, ".properties");
|
|
165
|
+
if (fileNameWithoutExtension === i18nBundleName) {
|
|
166
|
+
supportedLocales.push("");
|
|
167
|
+
} else if (fileNameWithoutExtension.startsWith(i18nBundlePrefix)) {
|
|
168
|
+
const locale = fileNameWithoutExtension.replace(i18nBundlePrefix, "");
|
|
169
|
+
supportedLocales.push(locale);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
return supportedLocales.sort();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async processBundleConfig({bundleConfig, fallbackBundleUrl, isTerminologyBundle = false, fallbackLocale}) {
|
|
176
|
+
const bundleUrl = getBundleUrlFromConfigObject(bundleConfig, this.manifest["sap.app"].id, fallbackBundleUrl);
|
|
177
|
+
if (!bundleUrl) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (bundleConfig.supportedLocales) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const supportedLocales = await this.getSupportedLocales(
|
|
185
|
+
bundleUrl, fallbackLocale ?? bundleConfig.fallbackLocale, isTerminologyBundle
|
|
186
|
+
);
|
|
187
|
+
if (supportedLocales.length > 0) {
|
|
188
|
+
bundleConfig.supportedLocales = supportedLocales;
|
|
189
|
+
this.markModified();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async getSupportedLocales(bundleUrl, fallbackLocale, isTerminologyBundle = false) {
|
|
194
|
+
// Ignore absolute URLs
|
|
195
|
+
if (isAbsoluteUrl(bundleUrl)) {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
const resolvedBundleUrl = resolveUI5Url(bundleUrl);
|
|
199
|
+
if (!resolvedBundleUrl) {
|
|
200
|
+
// In case of a relative ui5-protocol URL
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
const sapAppId = this.manifest["sap.app"].id;
|
|
204
|
+
const normalizedBundleUrl = normalizeBundleUrl(resolvedBundleUrl, sapAppId);
|
|
205
|
+
if (normalizedBundleUrl.startsWith("../")) {
|
|
206
|
+
log.verbose(
|
|
207
|
+
`${this.filePath}: ` +
|
|
208
|
+
`bundleUrl '${bundleUrl}' points to a bundle outside of the ` +
|
|
209
|
+
`current namespace '${sapAppId}', enhancement of 'supportedLocales' is skipped`
|
|
210
|
+
);
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
const supportedLocales = await this.findSupportedLocales(normalizedBundleUrl);
|
|
214
|
+
if (!isTerminologyBundle && supportedLocales.length > 0) {
|
|
215
|
+
if (fallbackLocale && !supportedLocales.includes(fallbackLocale)) {
|
|
216
|
+
log.error(
|
|
217
|
+
`${this.filePath}: ` +
|
|
218
|
+
`Generated supported locales ('${supportedLocales.join("', '")}') for ` +
|
|
219
|
+
`bundle '${normalizedBundleUrl}' ` +
|
|
220
|
+
"not containing the defined fallback locale '" + fallbackLocale + "'. Either provide a " +
|
|
221
|
+
"properties file for defined fallbackLocale or configure another available fallbackLocale"
|
|
222
|
+
);
|
|
223
|
+
return [];
|
|
224
|
+
} else if (!fallbackLocale && !supportedLocales.includes("en")) {
|
|
225
|
+
log.warn(
|
|
226
|
+
`${this.filePath}: ` +
|
|
227
|
+
`Generated supported locales ('${supportedLocales.join("', '")}') for ` +
|
|
228
|
+
`bundle '${normalizedBundleUrl}' ` +
|
|
229
|
+
"do not contain default fallback locale 'en'. Either provide a " +
|
|
230
|
+
"properties file for 'en' or configure another available fallbackLocale"
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return supportedLocales;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async processSapAppI18n() {
|
|
238
|
+
const sapApp = this.manifest["sap.app"];
|
|
239
|
+
let sapAppI18n = sapApp.i18n;
|
|
240
|
+
|
|
241
|
+
// Process enhanceWith bundles first, as they check for an existing supportedLocales property
|
|
242
|
+
// defined by the developer, but not the one generated by the tooling.
|
|
243
|
+
await this.processTerminologiesAndEnhanceWith(sapAppI18n);
|
|
244
|
+
|
|
245
|
+
const i18nBundleUrl = getBundleUrlFromConfig(sapAppI18n, sapApp.id, "i18n/i18n.properties");
|
|
246
|
+
|
|
247
|
+
if (!sapAppI18n?.supportedLocales && i18nBundleUrl) {
|
|
248
|
+
const supportedLocales = await this.getSupportedLocales(i18nBundleUrl, sapAppI18n?.fallbackLocale);
|
|
249
|
+
if (supportedLocales.length > 0) {
|
|
250
|
+
if (!sapAppI18n || typeof sapAppI18n === "string") {
|
|
251
|
+
sapAppI18n = sapApp.i18n = {
|
|
252
|
+
bundleUrl: i18nBundleUrl
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
sapAppI18n.supportedLocales = supportedLocales;
|
|
256
|
+
this.markModified();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Processes the terminologies and enhanceWith bundles of a bundle configuration.
|
|
263
|
+
*
|
|
264
|
+
* @param {object} bundleConfig
|
|
265
|
+
*/
|
|
266
|
+
async processTerminologiesAndEnhanceWith(bundleConfig) {
|
|
267
|
+
const bundleConfigs = [];
|
|
268
|
+
const terminologyBundleConfigs = [];
|
|
269
|
+
|
|
270
|
+
if (bundleConfig?.terminologies) {
|
|
271
|
+
terminologyBundleConfigs.push(...Object.values(bundleConfig.terminologies));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
bundleConfig?.enhanceWith?.forEach((config) => {
|
|
275
|
+
// The runtime logic propagates supportedLocales information to the enhanceWith bundles.
|
|
276
|
+
// In order to not break existing behavior, we do not generate supportedLocales for enhanceWith bundles
|
|
277
|
+
// in case the parent bundle does have supportedLocales defined.
|
|
278
|
+
if (!bundleConfig.supportedLocales) {
|
|
279
|
+
bundleConfigs.push({config, fallbackLocale: bundleConfig.fallbackLocale});
|
|
280
|
+
}
|
|
281
|
+
if (config.terminologies) {
|
|
282
|
+
terminologyBundleConfigs.push(...Object.values(config.terminologies));
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
await Promise.all(
|
|
287
|
+
bundleConfigs.map(({config, fallbackLocale}) => this.processBundleConfig({
|
|
288
|
+
bundleConfig: config,
|
|
289
|
+
fallbackLocale
|
|
290
|
+
}))
|
|
291
|
+
);
|
|
292
|
+
await Promise.all(
|
|
293
|
+
terminologyBundleConfigs.map((bundleConfig) => this.processBundleConfig({
|
|
294
|
+
bundleConfig, isTerminologyBundle: true
|
|
295
|
+
}))
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async processSapUi5Models() {
|
|
300
|
+
const sapUi5Models = this.manifest["sap.ui5"]?.models;
|
|
301
|
+
if (typeof sapUi5Models !== "object") {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const modelConfigs = Object.values(sapUi5Models)
|
|
305
|
+
.filter((modelConfig) => modelConfig.type === "sap.ui.model.resource.ResourceModel");
|
|
306
|
+
|
|
307
|
+
await Promise.all(
|
|
308
|
+
modelConfigs.map(async (modelConfig) => {
|
|
309
|
+
// Process enhanceWith bundles first, as they check for an existing supportedLocales property
|
|
310
|
+
// defined by the developer, but not the one generated by the tooling.
|
|
311
|
+
await this.processTerminologiesAndEnhanceWith(modelConfig.settings);
|
|
312
|
+
|
|
313
|
+
// Fallback to empty settings object in case only a "uri" is defined which will be converted
|
|
314
|
+
// to a settings object at runtime.
|
|
315
|
+
const settings = modelConfig.settings || {};
|
|
316
|
+
|
|
317
|
+
// Ensure to pass the "uri" property as fallback bundle URL according to the runtime logic.
|
|
318
|
+
// It is only taken into account if no "bundleUrl" or "bundleName" is defined.
|
|
319
|
+
await this.processBundleConfig({bundleConfig: settings, fallbackBundleUrl: modelConfig.uri});
|
|
320
|
+
|
|
321
|
+
// Ensure that the settings object is assigned back to the modelConfig
|
|
322
|
+
// in case it didn't existing before.
|
|
323
|
+
if (!modelConfig.settings) {
|
|
324
|
+
modelConfig.settings = settings;
|
|
325
|
+
}
|
|
326
|
+
})
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async processSapUi5LibraryI18n() {
|
|
331
|
+
let sapUi5LibraryI18n = this.manifest["sap.ui5"]?.library?.i18n;
|
|
332
|
+
|
|
333
|
+
// Process enhanceWith bundles first, as they check for an existing supportedLocales property
|
|
334
|
+
// defined by the developer, but not the one generated by the tooling.
|
|
335
|
+
await this.processTerminologiesAndEnhanceWith(sapUi5LibraryI18n);
|
|
336
|
+
|
|
337
|
+
const i18nBundleUrl = getBundleUrlFromSapUi5LibraryI18n(sapUi5LibraryI18n);
|
|
338
|
+
if (i18nBundleUrl && !sapUi5LibraryI18n?.supportedLocales) {
|
|
339
|
+
const supportedLocales = await this.getSupportedLocales(i18nBundleUrl, sapUi5LibraryI18n?.fallbackLocale);
|
|
340
|
+
if (supportedLocales.length > 0) {
|
|
341
|
+
if (!sapUi5LibraryI18n || typeof sapUi5LibraryI18n !== "object") {
|
|
342
|
+
this.manifest["sap.ui5"] ??= {};
|
|
343
|
+
this.manifest["sap.ui5"].library ??= {};
|
|
344
|
+
sapUi5LibraryI18n = this.manifest["sap.ui5"].library.i18n = {
|
|
345
|
+
bundleUrl: i18nBundleUrl
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
sapUi5LibraryI18n.supportedLocales = supportedLocales;
|
|
349
|
+
this.markModified();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async run() {
|
|
355
|
+
// Prevent multiple invocations
|
|
356
|
+
if (this.runInvoked) {
|
|
357
|
+
throw new Error("ManifestEnhancer#run can only be invoked once per instance");
|
|
358
|
+
}
|
|
359
|
+
this.runInvoked = true;
|
|
360
|
+
|
|
361
|
+
if (!this.manifest._version) {
|
|
362
|
+
log.verbose(`${this.filePath}: _version is not defined. No supportedLocales are generated`);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (lt(this.manifest._version, APP_DESCRIPTOR_V22)) {
|
|
367
|
+
log.verbose(`${this.filePath}: _version is lower than 1.21.0 so no supportedLocales can be generated`);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (this.manifest["sap.app"].type === "library") {
|
|
372
|
+
await this.processSapUi5LibraryI18n();
|
|
373
|
+
} else {
|
|
374
|
+
await Promise.all([
|
|
375
|
+
this.processSapAppI18n(),
|
|
376
|
+
this.processSapUi5Models()
|
|
377
|
+
]);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (this.isModified) {
|
|
381
|
+
return this.manifest;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* @module @ui5/builder/processors/manifestEnhancer
|
|
388
|
+
*/
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Enriches the content of the manifest.json file.
|
|
392
|
+
*
|
|
393
|
+
* @public
|
|
394
|
+
* @function default
|
|
395
|
+
* @static
|
|
396
|
+
*
|
|
397
|
+
* @param {object} parameters Parameters
|
|
398
|
+
* @param {@ui5/fs/Resource[]} parameters.resources List of manifest.json resources to be processed
|
|
399
|
+
* @param {fs|module:@ui5/fs/fsInterface} parameters.fs Node fs or custom
|
|
400
|
+
* [fs interface]{@link module:@ui5/fs/fsInterface}.
|
|
401
|
+
* @returns {Promise<Array<@ui5/fs/Resource|undefined>>} Promise resolving with an array of modified resources
|
|
402
|
+
*/
|
|
403
|
+
export default async function({resources, fs}) {
|
|
404
|
+
const res = await Promise.all(
|
|
405
|
+
resources.map(async (resource) => {
|
|
406
|
+
const manifest = await resource.getString();
|
|
407
|
+
const filePath = resource.getPath();
|
|
408
|
+
const manifestEnhancer = new ManifestEnhancer(manifest, filePath, fs);
|
|
409
|
+
const enrichedManifest = await manifestEnhancer.run();
|
|
410
|
+
if (enrichedManifest) {
|
|
411
|
+
resource.setString(JSON.stringify(enrichedManifest, null, 2));
|
|
412
|
+
return resource;
|
|
413
|
+
}
|
|
414
|
+
})
|
|
415
|
+
);
|
|
416
|
+
return res.filter(($) => $);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export const __internals__ = (process.env.NODE_ENV === "test") ?
|
|
420
|
+
{ManifestEnhancer, getRelativeBundleUrlFromName, normalizeBundleUrl, resolveUI5Url} : undefined;
|
|
@@ -42,6 +42,10 @@ function getPool(taskUtil) {
|
|
|
42
42
|
let {idleWorkers, totalWorkers} = pool.stats();
|
|
43
43
|
while (idleWorkers !== totalWorkers && !force) {
|
|
44
44
|
await setTimeoutPromise(100); // Wait a bit workers to finish and try again
|
|
45
|
+
|
|
46
|
+
if (!pool) { // pool might have been terminated in the meantime
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
45
49
|
({idleWorkers, totalWorkers} = pool.stats());
|
|
46
50
|
}
|
|
47
51
|
|
|
@@ -222,7 +226,7 @@ export default async function({
|
|
|
222
226
|
const sourceMapJson = JSON.parse(sourceMapContent);
|
|
223
227
|
|
|
224
228
|
if (sourceMapJson.sections) {
|
|
225
|
-
// TODO
|
|
229
|
+
// TODO 5.0
|
|
226
230
|
// Module "@jridgewell/trace-mapping" (used by Terser) can't handle index map sections lacking
|
|
227
231
|
// a "names" array. Since this is a common occurrence for UI5 Tooling bundles, we search for
|
|
228
232
|
// such cases here and fix them until https://github.com/jridgewell/trace-mapping/pull/29 is
|
package/lib/tasks/buildThemes.js
CHANGED
|
@@ -37,6 +37,10 @@ function getPool(taskUtil) {
|
|
|
37
37
|
let {idleWorkers, totalWorkers} = pool.stats();
|
|
38
38
|
while (idleWorkers !== totalWorkers && !force) {
|
|
39
39
|
await setTimeoutPromise(100); // Wait a bit workers to finish and try again
|
|
40
|
+
|
|
41
|
+
if (!pool) { // pool might have been terminated in the meantime
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
40
44
|
({idleWorkers, totalWorkers} = pool.stats());
|
|
41
45
|
}
|
|
42
46
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import moduleBundler from "../../processors/bundlers/moduleBundler.js";
|
|
2
|
+
import {applyDefaultsToBundleDefinition} from "./utils/applyDefaultsToBundleDefinition.js";
|
|
2
3
|
import createModuleNameMapping from "./utils/createModuleNameMapping.js";
|
|
3
4
|
import ReaderCollectionPrioritized from "@ui5/fs/ReaderCollectionPrioritized";
|
|
4
5
|
|
|
@@ -94,12 +95,20 @@ export default async function({
|
|
|
94
95
|
}
|
|
95
96
|
});
|
|
96
97
|
}
|
|
97
|
-
|
|
98
|
+
const coreVersion = taskUtil?.getProject("sap.ui.core")?.getVersion();
|
|
99
|
+
const allowStringBundling = taskUtil?.getProject().getSpecVersion().lt("4.0");
|
|
98
100
|
return combo.byGlob("/resources/**/*.{js,json,xml,html,properties,library,js.map}").then((resources) => {
|
|
99
|
-
const options = {
|
|
101
|
+
const options = {
|
|
102
|
+
bundleDefinition: applyDefaultsToBundleDefinition(bundleDefinition, taskUtil),
|
|
103
|
+
bundleOptions,
|
|
104
|
+
allowStringBundling
|
|
105
|
+
};
|
|
100
106
|
if (!optimize && taskUtil) {
|
|
101
107
|
options.moduleNameMapping = createModuleNameMapping({resources, taskUtil});
|
|
102
108
|
}
|
|
109
|
+
if (coreVersion) {
|
|
110
|
+
options.targetUi5CoreVersion = coreVersion;
|
|
111
|
+
}
|
|
103
112
|
return moduleBundler({options, resources}).then((bundles) => {
|
|
104
113
|
return Promise.all(bundles.map(({bundle, sourceMap} = {}) => {
|
|
105
114
|
if (!bundle) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import moduleBundler from "../../processors/bundlers/moduleBundler.js";
|
|
3
|
+
import {applyDefaultsToBundleDefinition} from "./utils/applyDefaultsToBundleDefinition.js";
|
|
3
4
|
import {getLogger} from "@ui5/logger";
|
|
4
5
|
const log = getLogger("builder:tasks:bundlers:generateComponentPreload");
|
|
5
6
|
import {negateFilters} from "../../lbt/resources/ResourceFilterList.js";
|
|
@@ -144,18 +145,24 @@ export default async function({
|
|
|
144
145
|
);
|
|
145
146
|
});
|
|
146
147
|
}
|
|
147
|
-
|
|
148
|
+
const coreVersion = taskUtil?.getProject("sap.ui.core")?.getVersion();
|
|
149
|
+
const allowStringBundling = taskUtil?.getProject().getSpecVersion().lt("4.0");
|
|
148
150
|
return Promise.all(bundleDefinitions.filter(Boolean).map((bundleDefinition) => {
|
|
149
151
|
log.verbose(`Generating ${bundleDefinition.name}...`);
|
|
152
|
+
const options = {
|
|
153
|
+
bundleDefinition: applyDefaultsToBundleDefinition(bundleDefinition, taskUtil),
|
|
154
|
+
bundleOptions: {
|
|
155
|
+
ignoreMissingModules: true,
|
|
156
|
+
optimize: true
|
|
157
|
+
},
|
|
158
|
+
allowStringBundling
|
|
159
|
+
};
|
|
160
|
+
if (coreVersion) {
|
|
161
|
+
options.targetUi5CoreVersion = coreVersion;
|
|
162
|
+
}
|
|
150
163
|
return moduleBundler({
|
|
151
164
|
resources,
|
|
152
|
-
options
|
|
153
|
-
bundleDefinition,
|
|
154
|
-
bundleOptions: {
|
|
155
|
-
ignoreMissingModules: true,
|
|
156
|
-
optimize: true
|
|
157
|
-
}
|
|
158
|
-
}
|
|
165
|
+
options
|
|
159
166
|
});
|
|
160
167
|
}));
|
|
161
168
|
})
|
|
@@ -29,8 +29,7 @@ import semver from "semver";
|
|
|
29
29
|
* @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
|
|
30
30
|
*/
|
|
31
31
|
export default async function({workspace, taskUtil, options = {}}) {
|
|
32
|
-
|
|
33
|
-
const namespace = options.projectNamespace || options.namespace;
|
|
32
|
+
const namespace = options.projectNamespace;
|
|
34
33
|
|
|
35
34
|
// Use the given namespace if available, otherwise use no namespace
|
|
36
35
|
// (e.g. in case no manifest.json is present)
|