Skip to content

Commit 2461b2c

Browse files
authored
[INTERNAL] generateLibraryPreload: Add experimental bundle-info preload (#1170)
For internal PoC usage. This change is not active in regular builds. To activate it, the environment variable `UI5_CLI_EXPERIMENTAL_BUNDLE_INFO_PRELOAD` needs to be set to any value.
1 parent 68908e0 commit 2461b2c

File tree

3 files changed

+192
-40
lines changed

3 files changed

+192
-40
lines changed

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
}

lib/tasks/bundlers/generateLibraryPreload.js

Lines changed: 184 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}`);
@@ -390,42 +481,99 @@ export default async function({workspace, taskUtil, options: {skipBundles = [],
390481
const libraryNamespaceMatch = libraryIndicatorPath.match(libraryNamespacePattern);
391482
if (libraryNamespaceMatch && libraryNamespaceMatch[1]) {
392483
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-
]);
484+
let results;
485+
if (!createBundleInfoPreload) {
486+
// Regular bundling
487+
results = await Promise.all([
488+
execModuleBundlerIfNeeded({
489+
options: {
490+
bundleDefinition: getBundleDefinition(libraryNamespace, excludes),
491+
bundleOptions: {
492+
optimize: true,
493+
ignoreMissingModules: true
494+
}
495+
},
496+
resources
497+
}),
498+
execModuleBundlerIfNeeded({
499+
options: {
500+
bundleDefinition: getDesigntimeBundleDefinition(libraryNamespace),
501+
bundleOptions: {
502+
optimize: true,
503+
ignoreMissingModules: true,
504+
skipIfEmpty: true
505+
}
506+
},
507+
resources
508+
}),
509+
execModuleBundlerIfNeeded({
510+
options: {
511+
bundleDefinition: getSupportFilesBundleDefinition(libraryNamespace),
512+
bundleOptions: {
513+
optimize: false,
514+
ignoreMissingModules: true,
515+
skipIfEmpty: true
516+
}
517+
// Note: Although the bundle uses optimize=false, there is
518+
// no moduleNameMapping needed, as support files are excluded from minification.
519+
},
520+
resources
521+
})
522+
]);
523+
} else {
524+
log.info(
525+
`Using experimental bundling with bundle info preload ` +
526+
`for library ${libraryNamespace} in project ${projectName}`);
527+
log.info(`Detected sap.ui.core version is ${coreVersion || "unknown"}`);
528+
// Experimental bundling with bundle info preload
529+
results = await Promise.all([
530+
execModuleBundlerIfNeeded({
531+
options: {
532+
bundleDefinition:
533+
getBundleInfoPreloadDefinition(libraryNamespace, excludes, coreVersion),
534+
bundleOptions: {
535+
optimize: true,
536+
ignoreMissingModules: true
537+
}
538+
},
539+
resources
540+
}),
541+
execModuleBundlerIfNeeded({
542+
options: {
543+
bundleDefinition: getContentBundleDefinition(libraryNamespace, excludes),
544+
bundleOptions: {
545+
optimize: true,
546+
ignoreMissingModules: true
547+
}
548+
},
549+
resources
550+
}),
551+
execModuleBundlerIfNeeded({
552+
options: {
553+
bundleDefinition: getDesigntimeBundleDefinition(libraryNamespace),
554+
bundleOptions: {
555+
optimize: true,
556+
ignoreMissingModules: true,
557+
skipIfEmpty: true
558+
}
559+
},
560+
resources
561+
}),
562+
execModuleBundlerIfNeeded({
563+
options: {
564+
bundleDefinition: getSupportFilesBundleDefinition(libraryNamespace),
565+
bundleOptions: {
566+
optimize: false,
567+
ignoreMissingModules: true,
568+
skipIfEmpty: true
569+
}
570+
// Note: Although the bundle uses optimize=false, there is
571+
// no moduleNameMapping needed, as support files are excluded from minification.
572+
},
573+
resources
574+
})
575+
]);
576+
}
429577
const bundles = Array.prototype.concat.apply([], results).filter(Boolean);
430578
return Promise.all(bundles.map(({bundle, sourceMap} = {}) => {
431579
if (bundle) {

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)