diff --git a/lib/lbt/bundle/Builder.js b/lib/lbt/bundle/Builder.js index 582023237..6d052e26f 100644 --- a/lib/lbt/bundle/Builder.js +++ b/lib/lbt/bundle/Builder.js @@ -37,6 +37,10 @@ function makeStringLiteral(str) { }) + "'"; } +function isEmptyBundle(resolvedBundle) { + return resolvedBundle.sections.every((section) => section.modules.length === 0); +} + const UI5BundleFormat = { beforePreloads(outW, section) { outW.write(`jQuery.sap.registerPreloadedModules(`); @@ -130,6 +134,10 @@ class BundleBuilder { async _createBundle(module, options) { const resolvedModule = await this.resolver.resolve(module, options); + if ( options.skipIfEmpty && isEmptyBundle(resolvedModule) ) { + log.verbose(" skipping empty bundle " + module.name); + return undefined; + } log.verbose(" create '%s'", resolvedModule.name); this.options = options || {}; diff --git a/lib/processors/bundlers/moduleBundler.js b/lib/processors/bundlers/moduleBundler.js index 62e7f24c0..d0bddbdb3 100644 --- a/lib/processors/bundlers/moduleBundler.js +++ b/lib/processors/bundlers/moduleBundler.js @@ -116,13 +116,16 @@ module.exports = function({resources, options: {bundleDefinition, bundleOptions} bundles = [results]; } - return Promise.all(bundles.map(function({name, content, bundleInfo}) { - // console.log("creating bundle as '%s'", "/resources/" + name); - const resource = new EvoResource({ - path: "/resources/" + name, - string: content - }); - return resource; + return Promise.all(bundles.map((bundleObj) => { + if ( bundleObj ) { + const {name, content} = bundleObj; + // console.log("creating bundle as '%s'", "/resources/" + name); + const resource = new EvoResource({ + path: "/resources/" + name, + string: content + }); + return resource; + } })); }); }; diff --git a/lib/tasks/bundlers/generateLibraryPreload.js b/lib/tasks/bundlers/generateLibraryPreload.js index b4158825e..d7fc56809 100644 --- a/lib/tasks/bundlers/generateLibraryPreload.js +++ b/lib/tasks/bundlers/generateLibraryPreload.js @@ -2,10 +2,24 @@ const log = require("@ui5/logger").getLogger("builder:tasks:bundlers:generateLib const moduleBundler = require("../../processors/bundlers/moduleBundler"); const ReaderCollectionPrioritized = require("@ui5/fs").ReaderCollectionPrioritized; +const DEFAULT_FILE_TYPES = [ + ".js", + ".control.xml", // XMLComposite + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties", + ".json" +]; + function getDefaultLibraryPreloadFilters(namespace) { return [ `${namespace}/`, `!${namespace}/.library`, + `!${namespace}/*-preload.js`, // exclude all bundles `!${namespace}/designtime/`, `!${namespace}/**/*.designtime.js`, `!${namespace}/**/*.support.js`, @@ -19,7 +33,7 @@ function getBundleDefinition(namespace) { if (namespace === "sap/ui/core") { return { name: `${namespace}/library-preload.js`, - defaultFileTypes: [".js", ".fragment.xml", ".view.xml", ".properties", ".json"], + defaultFileTypes: DEFAULT_FILE_TYPES, sections: [ { // exclude the content of sap-ui-core by declaring it as 'provided' @@ -71,7 +85,7 @@ function getBundleDefinition(namespace) { } return { name: `${namespace}/library-preload.js`, - defaultFileTypes: [".js", ".fragment.xml", ".view.xml", ".properties", ".json"], + defaultFileTypes: DEFAULT_FILE_TYPES, sections: [ { mode: "preload", @@ -84,6 +98,88 @@ function getBundleDefinition(namespace) { }; } +function getDesigntimeBundleDefinition(namespace) { + return { + name: `${namespace}/designtime/library-preload.designtime.js`, + defaultFileTypes: DEFAULT_FILE_TYPES, + sections: [ + { + mode: "preload", + filters: [ + `${namespace}/**/*.designtime.js`, + `${namespace}/designtime/`, + `!${namespace}/**/*-preload.designtime.js`, + `!${namespace}/designtime/**/*.properties`, + `!${namespace}/designtime/**/*.svg`, + `!${namespace}/designtime/**/*.xml` + ], + resolve: false, + resolveConditional: false, + renderer: false + } + ] + }; +} + +function getSupportFilesBundleDefinition(namespace) { + return { + name: `${namespace}/library-preload.support.js`, + defaultFileTypes: DEFAULT_FILE_TYPES, + sections: [ + { + mode: "preload", + filters: [ + `${namespace}/**/*.support.js`, + `!${namespace}/**/*-preload.support.js` + ], + resolve: false, + resolveConditional: false, + renderer: false + } + ] + }; +} + +function createLibraryBundles(libraryNamespace, resources) { + return Promise.all([ + moduleBundler({ + options: { + bundleDefinition: getBundleDefinition(libraryNamespace), + bundleOptions: { + optimize: true, + usePredefineCalls: true, + ignoreMissingModules: true + } + }, + resources + }), + moduleBundler({ + options: { + bundleDefinition: getDesigntimeBundleDefinition(libraryNamespace), + bundleOptions: { + optimize: true, + usePredefineCalls: true, + ignoreMissingModules: true, + skipIfEmpty: true + } + }, + resources + }), + moduleBundler({ + options: { + bundleDefinition: getSupportFilesBundleDefinition(libraryNamespace), + bundleOptions: { + optimize: false, + usePredefineCalls: true, + ignoreMissingModules: true, + skipIfEmpty: true + } + }, + resources + }) + ]); +} + function getModuleBundlerOptions(config) { const moduleBundlerOptions = {}; @@ -247,22 +343,11 @@ module.exports = function({workspace, dependencies, options: {projectName}}) { const libraryNamespaceMatch = libraryIndicatorPath.match(libraryNamespacePattern); if (libraryNamespaceMatch && libraryNamespaceMatch[1]) { const libraryNamespace = libraryNamespaceMatch[1]; - return moduleBundler({ - options: { - bundleDefinition: getBundleDefinition(libraryNamespace), - bundleOptions: { - optimize: true, - usePredefineCalls: true, - ignoreMissingModules: true - } - }, - resources - }).then(([bundle]) => { - if (bundle) { - // console.log(`${libraryNamespace}/library-preload.js bundle created`); - return workspace.write(bundle); - } - }); + return createLibraryBundles(libraryNamespace, resources) + .then((results) => { + const bundles = Array.prototype.concat.apply([], results); + return Promise.all(bundles.map((bundle) => bundle && workspace.write(bundle))); + }); } else { log.verbose(`Could not determine library namespace from file "${libraryIndicatorPath}" for project ${projectName}. Skipping library preload bundling.`); return Promise.resolve(); diff --git a/test/lib/tasks/bundlers/generateLibraryPreload.integration.js b/test/lib/tasks/bundlers/generateLibraryPreload.integration.js index c27980822..6fc816ac0 100644 --- a/test/lib/tasks/bundlers/generateLibraryPreload.integration.js +++ b/test/lib/tasks/bundlers/generateLibraryPreload.integration.js @@ -4,9 +4,13 @@ const chai = require("chai"); chai.use(require("chai-fs")); const assert = chai.assert; +const ui5Fs = require("@ui5/fs"); +const resourceFactory = ui5Fs.resourceFactory; +const DuplexCollection = ui5Fs.DuplexCollection; const ui5Builder = require("../../../../"); const builder = ui5Builder.builder; +const {generateLibraryPreload} = ui5Builder.tasks; const libraryDPath = path.join(__dirname, "..", "..", "..", "fixtures", "library.d"); const sapUiCorePath = path.join(__dirname, "..", "..", "..", "fixtures", "sap.ui.core"); @@ -129,3 +133,124 @@ const sapUiCoreTree = { } } }; + + +test("integration: generateLibraryPreload", async (t) => { + const reader = resourceFactory.createAdapter({ + virBasePath: "/" + }); + await reader.write(resourceFactory.createResource({ + path: "/resources/my/test/lib/library.js", + string: "" + })); + + const writer = resourceFactory.createAdapter({ + virBasePath: "/" + }); + const duplexCollection = new DuplexCollection({reader, writer}); + const dependencies = resourceFactory.createAdapter({ + virBasePath: "/" + }); + + await generateLibraryPreload({ + workspace: duplexCollection, + dependencies: dependencies, + options: { + projectName: "my.test.lib" + } + }); + + const writtenResources = await writer.byGlob(["**/**"]); + t.deepEqual(writtenResources.map((r) => r.getPath()).sort(), [ + "/resources/my/test/lib/library-preload.js" + ], "Expected preload files should be created"); + + const libraryPreload = await writer.byPath("/resources/my/test/lib/library-preload.js"); + t.truthy(libraryPreload, "library-preload.js should have been created"); + const libraryPreloadContent = await libraryPreload.getString(); + t.true(libraryPreloadContent.includes("//@ui5-bundle my/test/lib/library-preload.js"), + "library-preload should be a bundle"); + t.regex(libraryPreloadContent, new RegExp("my/test/lib/library"), + "library-preload should include library.js module"); +}); + +test("integration: generateLibraryPreload with designtime and support files", async (t) => { + const reader = resourceFactory.createAdapter({ + virBasePath: "/" + }); + await reader.write(resourceFactory.createResource({ + path: "/resources/my/test/lib/library.js", + string: "" + })); + + // designtime + await reader.write(resourceFactory.createResource({ + path: "/resources/my/test/lib/designtime/foo.js", + string: "" + })); + await reader.write(resourceFactory.createResource({ + path: "/resources/my/test/lib/some.designtime.js", + string: "" + })); + + // support + await reader.write(resourceFactory.createResource({ + path: "/resources/my/test/lib/some.support.js", + string: "" + })); + await reader.write(resourceFactory.createResource({ + path: "/resources/my/test/lib/support/foo.support.js", + string: "" + })); + + const writer = resourceFactory.createAdapter({ + virBasePath: "/" + }); + const duplexCollection = new DuplexCollection({reader, writer}); + const dependencies = resourceFactory.createAdapter({ + virBasePath: "/" + }); + + await generateLibraryPreload({ + workspace: duplexCollection, + dependencies: dependencies, + options: { + projectName: "my.test.lib" + } + }); + + const writtenResources = await writer.byGlob(["**/**"]); + t.deepEqual(writtenResources.map((r) => r.getPath()).sort(), [ + "/resources/my/test/lib/designtime/library-preload.designtime.js", + "/resources/my/test/lib/library-preload.js", + "/resources/my/test/lib/library-preload.support.js" + ], "Expected preload files should be created"); + + const libraryPreload = await writer.byPath("/resources/my/test/lib/library-preload.js"); + t.truthy(libraryPreload, "library-preload.js should have been created"); + const libraryPreloadContent = await libraryPreload.getString(); + t.true(libraryPreloadContent.includes("//@ui5-bundle my/test/lib/library-preload.js"), + "library-preload should be a bundle"); + t.regex(libraryPreloadContent, new RegExp("my/test/lib/library"), + "library-preload should include library.js module"); + + const designtimePreload = await writer.byPath("/resources/my/test/lib/designtime/library-preload.designtime.js"); + t.truthy(designtimePreload, "library-preload.js should have been created"); + const designtimePreloadContent = await designtimePreload.getString(); + t.true(designtimePreloadContent.includes("//@ui5-bundle my/test/lib/designtime/library-preload.designtime.js"), + "library-preload.designtime should be a bundle"); + t.regex(designtimePreloadContent, new RegExp("my/test/lib/designtime/foo"), + "library-preload should include designtime/foo module"); + t.regex(designtimePreloadContent, new RegExp("my/test/lib/some\\.designtime"), + "library-preload should include some.designtime module"); + + const supportPreload = await writer.byPath("/resources/my/test/lib/library-preload.support.js"); + t.truthy(supportPreload, "library-preload.js should have been created"); + const supportPreloadContent = await supportPreload.getString(); + t.true(supportPreloadContent.includes("//@ui5-bundle my/test/lib/library-preload.support.js"), + "library-preload.support should be a bundle"); + t.regex(supportPreloadContent, new RegExp("my/test/lib/some\\.support"), + "library-preload.support should include some.support module"); + t.regex(supportPreloadContent, new RegExp("my/test/lib/support/foo\\.support"), + "library-preload.support should include support/foo.support module"); +}); diff --git a/test/lib/tasks/bundlers/generateLibraryPreload.js b/test/lib/tasks/bundlers/generateLibraryPreload.js index 64ff0f3e2..0f70fd889 100644 --- a/test/lib/tasks/bundlers/generateLibraryPreload.js +++ b/test/lib/tasks/bundlers/generateLibraryPreload.js @@ -52,16 +52,21 @@ test.serial("generateLibraryPreload", async (t) => { } }); - t.is(moduleBundlerStub.callCount, 1, "moduleBundler should have been called once"); + t.is(moduleBundlerStub.callCount, 3, "moduleBundler should have been called 3 times"); t.deepEqual(moduleBundlerStub.getCall(0).args, [{ options: { bundleDefinition: { defaultFileTypes: [ ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", ".fragment.xml", + ".view.html", + ".view.json", ".view.xml", ".properties", - ".json", + ".json" ], name: "my/lib/library-preload.js", sections: [ @@ -69,6 +74,7 @@ test.serial("generateLibraryPreload", async (t) => { filters: [ "my/lib/", "!my/lib/.library", + "!my/lib/*-preload.js", "!my/lib/designtime/", "!my/lib/**/*.designtime.js", "!my/lib/**/*.support.js", @@ -127,7 +133,7 @@ test("generateLibraryPreload for sap.ui.core (w/o ui5loader.js)", async (t) => { } }); - t.is(moduleBundlerStub.callCount, 5, "moduleBundler should have been called 5 times"); + t.is(moduleBundlerStub.callCount, 7, "moduleBundler should have been called 7 times"); t.deepEqual(moduleBundlerStub.getCall(0).args, [{ options: { bundleDefinition: { @@ -293,10 +299,15 @@ test("generateLibraryPreload for sap.ui.core (w/o ui5loader.js)", async (t) => { bundleDefinition: { defaultFileTypes: [ ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", ".fragment.xml", + ".view.html", + ".view.json", ".view.xml", ".properties", - ".json", + ".json" ], name: "sap/ui/core/library-preload.js", sections: [ @@ -312,6 +323,7 @@ test("generateLibraryPreload for sap.ui.core (w/o ui5loader.js)", async (t) => { filters: [ "sap/ui/core/", "!sap/ui/core/.library", + "!sap/ui/core/*-preload.js", "!sap/ui/core/designtime/", "!sap/ui/core/**/*.designtime.js", "!sap/ui/core/**/*.support.js", @@ -394,7 +406,7 @@ test("generateLibraryPreload for sap.ui.core (/w ui5loader.js)", async (t) => { } }); - t.is(moduleBundlerStub.callCount, 5, "moduleBundler should have been called 5 times"); + t.is(moduleBundlerStub.callCount, 7, "moduleBundler should have been called 5 times"); t.deepEqual(moduleBundlerStub.getCall(0).args, [{ options: { bundleDefinition: { @@ -560,10 +572,15 @@ test("generateLibraryPreload for sap.ui.core (/w ui5loader.js)", async (t) => { bundleDefinition: { defaultFileTypes: [ ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", ".fragment.xml", + ".view.html", + ".view.json", ".view.xml", ".properties", - ".json", + ".json" ], name: "sap/ui/core/library-preload.js", sections: [ @@ -579,6 +596,7 @@ test("generateLibraryPreload for sap.ui.core (/w ui5loader.js)", async (t) => { filters: [ "sap/ui/core/", "!sap/ui/core/.library", + "!sap/ui/core/*-preload.js", "!sap/ui/core/designtime/", "!sap/ui/core/**/*.designtime.js", "!sap/ui/core/**/*.support.js", @@ -622,6 +640,86 @@ test("generateLibraryPreload for sap.ui.core (/w ui5loader.js)", async (t) => { }, resources: filteredResources }]); + t.deepEqual(moduleBundlerStub.getCall(5).args, [{ + options: { + bundleDefinition: { + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties", + ".json" + ], + name: "sap/ui/core/designtime/library-preload.designtime.js", + sections: [ + { + filters: [ + "sap/ui/core/**/*.designtime.js", + "sap/ui/core/designtime/", + "!sap/ui/core/**/*-preload.designtime.js", + "!sap/ui/core/designtime/**/*.properties", + "!sap/ui/core/designtime/**/*.svg", + "!sap/ui/core/designtime/**/*.xml" + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + } + ] + }, + bundleOptions: { + optimize: true, + usePredefineCalls: true, + ignoreMissingModules: true, + skipIfEmpty: true + } + }, + resources: filteredResources + }]); + t.deepEqual(moduleBundlerStub.getCall(6).args, [{ + options: { + bundleDefinition: { + defaultFileTypes: [ + ".js", + ".control.xml", + ".fragment.html", + ".fragment.json", + ".fragment.xml", + ".view.html", + ".view.json", + ".view.xml", + ".properties", + ".json" + ], + name: "sap/ui/core/library-preload.support.js", + sections: [ + { + filters: [ + "sap/ui/core/**/*.support.js", + "!sap/ui/core/**/*-preload.support.js" + ], + mode: "preload", + renderer: false, + resolve: false, + resolveConditional: false, + } + ] + }, + bundleOptions: { + optimize: false, + usePredefineCalls: true, + ignoreMissingModules: true, + skipIfEmpty: true + } + }, + resources: filteredResources + }]); t.is(workspace.byGlob.callCount, 1, "workspace.byGlob should have been called once");