Skip to content

Commit 7213929

Browse files
committed
refactor(builder): Add experimental bundle-info preload
Upport of * SAP/ui5-builder#1170 * SAP/ui5-builder#1174
1 parent fdc073b commit 7213929

File tree

5 files changed

+224
-41
lines changed

5 files changed

+224
-41
lines changed

packages/builder/lib/lbt/bundle/Resolver.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class BundleResolver {
5656
* in the resource pool.
5757
*/
5858
const missingModules = Object.create(null);
59+
const ignoreMissingModules = pool.getIgnoreMissingModules();
5960
/**
6061
* Names of modules that are included in non-decomposable bundles.
6162
* If they occur in the missingModules, then this is not an error.
@@ -123,7 +124,7 @@ class BundleResolver {
123124
done = pool.findResourceWithInfo(resourceName)
124125
.catch( (err) => {
125126
// if the caller provided an error message, log it
126-
if ( msg ) {
127+
if ( msg && !ignoreMissingModules ) {
127128
missingModules[resourceName] ??= [];
128129
missingModules[resourceName].push(msg);
129130
}

packages/builder/lib/processors/manifestCreator.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -491,11 +491,18 @@ async function createManifest(
491491
}
492492
}
493493

494-
return {
494+
const libraryMetadata = {
495495
i18n: i18n(),
496496
css: css(),
497497
content: content()
498498
};
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;
499506
}
500507

501508
const sapUI5 = {

packages/builder/lib/processors/manifestEnhancer.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,12 @@ class ManifestEnhancer {
441441

442442
if (this.manifest["sap.app"].type === "library") {
443443
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+
}
444450
} else {
445451
await Promise.all([
446452
this.processSapAppI18n(),

packages/builder/lib/tasks/bundlers/generateLibraryPreload.js

Lines changed: 202 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import semver from "semver";
12
import {getLogger} from "@ui5/logger";
23
const log = getLogger("builder:tasks:bundlers:generateLibraryPreload");
34
import moduleBundler from "../../processors/bundlers/moduleBundler.js";
@@ -32,6 +33,32 @@ function getDefaultLibraryPreloadFilters(namespace, excludes) {
3233
return filters;
3334
}
3435

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+
3562
function getBundleDefinition(namespace, excludes) {
3663
// Note: This configuration is only used when no bundle definition in ui5.yaml exists (see "skipBundles" parameter)
3764

@@ -106,6 +133,69 @@ function getBundleDefinition(namespace, excludes) {
106133
};
107134
}
108135

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+
109199
function getDesigntimeBundleDefinition(namespace) {
110200
return {
111201
name: `${namespace}/designtime/library-preload.designtime.js`,
@@ -258,6 +348,7 @@ export default async function({workspace, taskUtil, options: {skipBundles = [],
258348
}
259349
const coreVersion = taskUtil?.getProject("sap.ui.core")?.getVersion();
260350
const allowStringBundling = taskUtil?.getProject().getSpecVersion().lt("4.0");
351+
const createBundleInfoPreload = !!process.env.UI5_CLI_EXPERIMENTAL_BUNDLE_INFO_PRELOAD;
261352
const execModuleBundlerIfNeeded = ({options, resources}) => {
262353
if (skipBundles.includes(options.bundleDefinition.name)) {
263354
log.verbose(`Skipping generation of bundle ${options.bundleDefinition.name}`);
@@ -392,42 +483,117 @@ export default async function({workspace, taskUtil, options: {skipBundles = [],
392483
const libraryNamespaceMatch = libraryIndicatorPath.match(libraryNamespacePattern);
393484
if (libraryNamespaceMatch && libraryNamespaceMatch[1]) {
394485
const libraryNamespace = libraryNamespaceMatch[1];
395-
const results = await Promise.all([
396-
execModuleBundlerIfNeeded({
397-
options: {
398-
bundleDefinition: getBundleDefinition(libraryNamespace, excludes),
399-
bundleOptions: {
400-
optimize: true,
401-
ignoreMissingModules: true
402-
}
403-
},
404-
resources
405-
}),
406-
execModuleBundlerIfNeeded({
407-
options: {
408-
bundleDefinition: getDesigntimeBundleDefinition(libraryNamespace),
409-
bundleOptions: {
410-
optimize: true,
411-
ignoreMissingModules: true,
412-
skipIfEmpty: true
413-
}
414-
},
415-
resources
416-
}),
417-
execModuleBundlerIfNeeded({
418-
options: {
419-
bundleDefinition: getSupportFilesBundleDefinition(libraryNamespace),
420-
bundleOptions: {
421-
optimize: false,
422-
ignoreMissingModules: true,
423-
skipIfEmpty: true
424-
}
425-
// Note: Although the bundle uses optimize=false, there is
426-
// no moduleNameMapping needed, as support files are excluded from minification.
427-
},
428-
resources
429-
})
430-
]);
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+
}
431597
const bundles = Array.prototype.concat.apply([], results).filter(Boolean);
432598
return Promise.all(bundles.map(({bundle, sourceMap} = {}) => {
433599
if (bundle) {

packages/builder/test/lib/lbt/bundle/AutoSplitter.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ function createMockPool(dependencies) {
3838
name: "x.view.xml"
3939
}, {
4040
name: "c.properties"
41-
}]
41+
}],
42+
getIgnoreMissingModules: () => false,
4243
};
4344
}
4445

@@ -165,7 +166,8 @@ test("integration: Extreme AutoSplitter with numberOfParts 50", async (t) => {
165166
});
166167
return {info};
167168
},
168-
resources: modules.map((res) => ({name: res}))
169+
resources: modules.map((res) => ({name: res})),
170+
getIgnoreMissingModules: () => false,
169171
};
170172
const autoSplitter = new AutoSplitter(pool, new BundleResolver(pool));
171173
const bundleDefinition = {
@@ -208,7 +210,8 @@ test("integration: AutoSplitter with bundleInfo", async (t) => {
208210
const info = new ModuleInfo(name);
209211
return {info};
210212
},
211-
resources: modules.map((res) => ({name: res}))
213+
resources: modules.map((res) => ({name: res})),
214+
getIgnoreMissingModules: () => false,
212215
};
213216
const autoSplitter = new AutoSplitter(pool, new BundleResolver(pool));
214217
const bundleDefinition = {

0 commit comments

Comments
 (0)