Skip to content

Commit

Permalink
[FEATURE] Support writing 'bundles' config as part of a bundle (#396)
Browse files Browse the repository at this point in the history
Co-authored-by: KlattG <57760635+KlattG@users.noreply.github.com>
Co-authored-by: Merlin Beutlberger <m.beutlberger@sap.com>
Co-authored-by: Sven Bender <sven.bender@sap.com>
  • Loading branch information
4 people authored Jun 1, 2021
1 parent cac5df9 commit b5f372a
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 10 deletions.
54 changes: 52 additions & 2 deletions lib/lbt/bundle/Builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ const UI5BundleFormat = {
outW.writeln(`}});`);
},

beforeBundleInfo(outW) {
outW.writeln("\"unsupported\"; /* 'bundleInfo' section mode not supported (requires ui5loader)");
},

afterBundleInfo(outW) {
outW.writeln("*/");
},

requireSync(outW, moduleName) {
outW.writeln(`sap.ui.requireSync("${ModuleName.toRequireJSName(moduleName)}");`);
},
Expand All @@ -77,6 +85,14 @@ const EVOBundleFormat = {
outW.writeln(`);`);
},

beforeBundleInfo(outW) {
outW.writeln("sap.ui.loader.config({bundlesUI5:{");
},

afterBundleInfo(outW) {
outW.writeln("}});");
},

requireSync(outW, moduleName) {
outW.writeln(`sap.ui.requireSync("${ModuleName.toRequireJSName(moduleName)}");`);
},
Expand Down Expand Up @@ -141,12 +157,24 @@ class BundleBuilder {
// TODO is the following condition ok or should the availability of jquery.sap.global.js be configurable?
this.jqglobalAvailable = !resolvedModule.containsGlobal;
this.openModule(resolvedModule.name);

let bundleInfos = [];
// create all sections in sequence
for ( const section of resolvedModule.sections ) {
log.verbose(" adding section%s of type %s",
section.name ? " '" + section.name + "'" : "", section.mode);
await this.addSection(section);
if ( section.mode === SectionType.BundleInfo ) {
bundleInfos.push(section);
} else {
if ( bundleInfos.length > 0 ) {
await this.writeBundleInfos(bundleInfos);
bundleInfos = [];
}
await this.addSection(section);
}
}
if ( bundleInfos.length > 0 ) {
await this.writeBundleInfos(bundleInfos);
bundleInfos = [];
}

this.closeModule(resolvedModule);
Expand Down Expand Up @@ -204,6 +232,8 @@ class BundleBuilder {
return this.writeRaw(section);
case SectionType.Preload:
return this.writePreloadFunction(section);
case SectionType.BundleInfo:
return this.writeBundleInfos([section]);
case SectionType.Require:
return this.writeRequires(section);
default:
Expand Down Expand Up @@ -444,6 +474,26 @@ class BundleBuilder {
});
}

writeBundleInfos(sections) {
this.outW.ensureNewLine();

if ( sections.length > 0 ) {
this.targetBundleFormat.beforeBundleInfo(this.outW);
sections.forEach((section, idx) => {
if ( idx > 0 ) {
this.outW.writeln(",");
}

if (!section.name) {
throw new Error(`A 'bundleInfo' section is missing the mandatory 'name' property.` );
}
this.outW.write(`"${section.name}":[${section.modules.map(makeStringLiteral).join(",")}]`);
});
this.outW.writeln();
this.targetBundleFormat.afterBundleInfo(this.outW);
}
}

writeRequires(section) {
this.outW.ensureNewLine();
section.modules.forEach( (module) => {
Expand Down
9 changes: 8 additions & 1 deletion lib/lbt/bundle/BundleDefinition.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ const SectionType = {
Preload: "preload",

/**
* For each module, a jQuery.sap.require call will be created.
* Content information for another bundle is written.
* Requires UI5 version 1.74.0 which adds runtime support for the 'bundles' and 'bundlesUI5'
* ui5loader configuration.
*/
BundleInfo: "bundleInfo",

/**
* For each module, a require call will be created.
* Usually used as the last section in a merged module to enforce loading and
* execution of some specific module or modules.
*/
Expand Down
21 changes: 14 additions & 7 deletions lib/processors/bundlers/moduleBundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ const EvoResource = require("@ui5/fs").Resource;
const log = require("@ui5/logger").getLogger("builder:processors:bundlers:moduleBundler");

/**
* A ModuleBundleDefinitionSection specifies the embedding mode ('provided', 'raw', 'preload' or 'require')
* and lists the resources that should be in- or excluded from the section.
* A ModuleBundleDefinitionSection specifies the embedding mode (either 'provided', 'raw', 'preload', 'require'
* or 'bundleInfo') and lists the resources that should be in- or excluded from the section.
* <p>
* <b>Module bundle section modes</b><br>
* <ul>
Expand All @@ -15,29 +15,36 @@ const log = require("@ui5/logger").getLogger("builder:processors:bundlers:module
* which the bundle module is loaded.
* </li>
* <li>
* <code>raw</code>: A raw section determines the set of modules that should be embedded, sorts them according
* <code>raw</code>: A 'raw' section determines the set of modules that should be embedded, sorts them according
* to their dependencies and writes them out 1:1 without any transformation or wrapping (raw). Only JavaScript
* sources can be embedded in a raw section.
* </li>
* <li>
* <code>preload</code>: A preload section packages resources that should be stored in the preload cache in the
* <code>preload</code>: A 'preload' section packages resources that should be stored in the preload cache in the
* client. They can embed any textual resource type (JavaScript, XML, JSON and .properties files) that the
* bundling supports. UI5 modules are wrapped into a 'sap.ui.predefine' call. Other JavaScript modules will be
* embedded into a 'jQuery.sap.registerPreload' call, unless the asynchronous ui5loader is used. With the
* ui5loader 'sap.ui.require.preload' is used for other modules.
* embedded into a 'jQuery.sap.registerPreload' call, or in a "sap.ui.require.preload" call when
* the ui5loader is available.
* </li>
* <li>
* <code>require</code>: A 'require' section is transformed into a sequence of jQuery.sap.require calls. The
* list will be resolved like an include pattern list in any of the other sections and for each of the resolved
* modules, a jQuery.sap.require will be created. In case the ui5loader is available, 'sap.ui.requireSync' is
* used instead.
* </li>
* <li>
* <code>bundleInfo</code>: A 'bundleInfo' section describes the content of another named bundle. This information
* is transformed into a ui5loader-"bundlesUI5" configuration.
* At runtime, if a module is known to be contained in a bundle, the loader will require that bundle before
* the module itself.
* This requires the ui5loader to be available at build time and UI5 version 1.74.0 or higher at runtime.
* </li>
* </ul>
* </p>
*
* @public
* @typedef {object} ModuleBundleDefinitionSection
* @property {string} mode The embedding mode. Either 'provided', 'raw', 'preload' or 'require'
* @property {string} mode The embedding mode. Either 'provided', 'raw', 'preload', 'require' or 'bundleInfo'
* @property {string[]} filters List of modules declared as glob patterns (resource name patterns) that should be
* in- or excluded.
* A pattern ending with a slash '/' will, similarly to the use of a single '*' or double '**' asterisk,
Expand Down
70 changes: 70 additions & 0 deletions test/lib/lbt/bundle/Builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -549,3 +549,73 @@ if (oError.name != "Restart") { throw oError; }
t.deepEqual(oResult.bundleInfo.subModules, ["jquery.sap.global.js", "myRawModule.js", "sap/ui/core/Core.js"],
"bundle info subModules are correct");
});

test("integration: createBundle with bundleInfo", async (t) => {
const pool = new ResourcePool();
pool.addResource({
name: "a.js",
buffer: async () => "function One(){return 1;}"
});
pool.addResource({
name: "b.js",
buffer: async () => "function Two(){return 2;}"
});
pool.addResource({
name: "ui5loader.js",
buffer: async () => ""
});
pool.addResource({
name: "a.library",
buffer: async () => `<?xml version="1.0" encoding="UTF-8" ?>
<library xmlns="http://www.sap.com/sap.ui.library.xsd" >
<appData>
<packaging xmlns="http://www.sap.com/ui5/buildext/packaging" version="2.0" >
<module-infos>
<raw-module name="a.js"
requiresTopLevelScope="false" />
</module-infos>
</packaging>
</appData>
</library>`
});

const bundleDefinition = {
name: `library-preload.js`,
defaultFileTypes: [".js"],
sections: [{
mode: "preload",
name: "preload-section",
filters: ["a.js"]
}, {
mode: "require",
filters: ["ui5loader.js"]
}, {
mode: "bundleInfo",
name: "my-custom-bundle",
filters: ["b.js"]
}]
};

const builder = new Builder(pool);
const oResult = await builder.createBundle(bundleDefinition, {numberOfParts: 1, decorateBootstrapModule: true});
t.deepEqual(oResult.name, "library-preload.js");
const expectedContent = `//@ui5-bundle library-preload.js
sap.ui.require.preload({
"a.js":function(){function One(){return 1;}
this.One=One;
}
},"preload-section");
sap.ui.requireSync("ui5loader");
sap.ui.loader.config({bundlesUI5:{
"my-custom-bundle":['b.js']
}});
`;
t.deepEqual(oResult.content, expectedContent, "EVOBundleFormat " +
"should contain:" +
" preload part from a.js" +
" require part from ui5loader.js");
t.deepEqual(oResult.bundleInfo.name, "library-preload.js", "bundle info name is correct");
t.deepEqual(oResult.bundleInfo.size, expectedContent.length, "bundle info size is correct");
t.deepEqual(oResult.bundleInfo.subModules, ["a.js", "b.js"],
"bundle info subModules are correct");
});

0 comments on commit b5f372a

Please sign in to comment.