Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Create designtime and support bundles for libraries #529

Merged
merged 3 commits into from
Oct 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The general ".json" file type is NOT present in our Maven tooling. I've kept it nevertheless for compatibility with existing projects that use the ui5-tooling already.

];

function getDefaultLibraryPreloadFilters(namespace) {
return [
`${namespace}/`,
`!${namespace}/.library`,
`!${namespace}/*-preload.js`, // exclude all bundles
matz3 marked this conversation as resolved.
Show resolved Hide resolved
`!${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)));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Due to the new skipIfEmpty, some of the entries in the bundles array might be undefined.

});
} 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