Skip to content

Commit

Permalink
[FEATURE] Create designtime and support bundles for libraries (#529)
Browse files Browse the repository at this point in the history
When a bundle would be empty (not contain any modules), it will be
skipped.

Co-authored-by: Matthias Osswald <mat.osswald@sap.com>
  • Loading branch information
codeworrior and matz3 authored Oct 20, 2020
1 parent 887255a commit 2a51943
Show file tree
Hide file tree
Showing 5 changed files with 350 additions and 31 deletions.
8 changes: 8 additions & 0 deletions lib/lbt/bundle/Builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(`);
Expand Down Expand Up @@ -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 || {};
Expand Down
17 changes: 10 additions & 7 deletions lib/processors/bundlers/moduleBundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}));
});
};
121 changes: 103 additions & 18 deletions lib/tasks/bundlers/generateLibraryPreload.js
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand All @@ -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'
Expand Down Expand Up @@ -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",
Expand All @@ -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 = {};

Expand Down Expand Up @@ -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();
Expand Down
125 changes: 125 additions & 0 deletions test/lib/tasks/bundlers/generateLibraryPreload.integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -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");
});
Loading

0 comments on commit 2a51943

Please sign in to comment.