Skip to content

Commit

Permalink
[INTERNAL] Improve handling of apps with no namespace configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
RandomByte committed Jan 21, 2019
1 parent bb2fcf2 commit 184fe52
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 96 deletions.
4 changes: 3 additions & 1 deletion lib/tasks/bundlers/generateComponentPreload.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ module.exports = function({workspace, dependencies, options}) {
}

namespaces = Array.prototype.concat.apply([], namespaces);
if (!namespaces || !namespaces.length) {
// As this task is often called with a single namespace, also check
// for bad calls like "namespaces: [undefined]"
if (!namespaces || !namespaces.length || !namespaces[0]) {
throw new Error("generateComponentPreload: No component namespace(s) " +
`found for project: ${options.projectName}`);
}
Expand Down
121 changes: 62 additions & 59 deletions lib/tasks/bundlers/generateStandaloneAppBundle.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const log = require("@ui5/logger").getLogger("builder:tasks:bundlers:generateStandaloneAppBundle");
const moduleBundler = require("../../processors/bundlers/moduleBundler");
const ReaderCollectionPrioritized = require("@ui5/fs").ReaderCollectionPrioritized;

/**
* Task for bundling standalone applications.
Expand All @@ -11,68 +11,71 @@ const ReaderCollectionPrioritized = require("@ui5/fs").ReaderCollectionPrioritiz
* @param {module:@ui5/fs.AbstractReader} parameters.dependencies Reader or Collection to read dependency files
* @param {Object} parameters.options Options
* @param {string} parameters.options.projectName Project name
* @param {string} parameters.options.namespace Project namespace
* @param {string} [parameters.options.namespace] Project namespace
* @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
*/
module.exports = function({workspace, dependencies, options}) {
const combo = new ReaderCollectionPrioritized({
name: `appBundlerStandalone - prioritize workspace over dependencies: ${options.projectName}`,
readers: [workspace, dependencies]
module.exports = async function({workspace, dependencies, options}) {
if (!options.namespace) {
log.warn(`Namespace of project ${options.projectName} is not known. Self contained bundling is currently ` +
`unable to generate complete bundles for such projects.`);
}

// If an application does not have a namespace, its resources are located at the root. Otherwise in /resources
// For dependencies, we do not want to search in their test-resources
const results = await Promise.all([
workspace.byGlob("/**/*.{js,json,xml,html,properties,library}"),
dependencies.byGlob("/resources/**/*.{js,json,xml,html,properties,library}")
]);
const resources = Array.prototype.concat.apply([], results);

const isEvo = resources.find((resource) => {
return resource.getPath() === "/resources/ui5loader.js";
});
return combo.byGlob("/resources/**/*.{js,json,xml,html,properties,library}")
.then((resources) => {
const isEvo = resources.find((resource) => {
return resource.getPath() === "/resources/ui5loader.js";
});
let filters;
if (isEvo) {
filters = ["ui5loader-autoconfig.js"];
} else {
filters = ["jquery.sap.global.js"];
}
let filters;
if (isEvo) {
filters = ["ui5loader-autoconfig.js"];
} else {
filters = ["jquery.sap.global.js"];
}

return moduleBundler({
resources,
options: {
bundleDefinition: {
name: "sap-ui-custom.js",
defaultFileTypes: [".js", ".fragment.xml", ".view.xml", ".properties", ".json"],
sections: [
{
// include all 'raw' modules that are needed for the UI5 loader
mode: "raw",
filters,
resolve: true, // dependencies for raw modules are taken from shims in .library files
sort: true, // topological sort on raw modules is mandatory
declareModules: false
},
{
mode: "preload",
filters: [
`${options.namespace}/`,
`!${options.namespace}/test/`,
`!${options.namespace}/*.html`,
"sap/ui/core/Core.js"
],
resolve: true,
resolveConditional: true,
renderer: true
},
// finally require and execute sap/ui/core/Core
{
mode: "require",
filters: [
"sap/ui/core/Core.js"
]
}
const processedResources = await moduleBundler({
resources,
options: {
bundleDefinition: {
name: "sap-ui-custom.js",
defaultFileTypes: [".js", ".fragment.xml", ".view.xml", ".properties", ".json"],
sections: [
{
// include all 'raw' modules that are needed for the UI5 loader
mode: "raw",
filters,
resolve: true, // dependencies for raw modules are taken from shims in .library files
sort: true, // topological sort on raw modules is mandatory
declareModules: false
},
{
mode: "preload",
filters: [
`${options.namespace || ""}/`,
`!${options.namespace || ""}/test/`,
`!${options.namespace || ""}/*.html`,
"sap/ui/core/Core.js"
],
resolve: true,
resolveConditional: true,
renderer: true
},
// finally require and execute sap/ui/core/Core
{
mode: "require",
filters: [
"sap/ui/core/Core.js"
]
}
}
});
})
.then((processedResources) => {
return Promise.all(processedResources.map((resource) => {
return workspace.write(resource);
}));
});
]
}
}
});

await Promise.all(processedResources.map((resource) => workspace.write(resource)));
};
12 changes: 7 additions & 5 deletions lib/tasks/transformBootstrapHtml.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ const bootstrapHtmlTransformer = require("../processors/bootstrapHtmlTransformer
* @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files
* @param {Object} parameters.options Options
* @param {string} parameters.options.projectName Project name
* @param {string} parameters.options.namespace Project namespace
* @param {string} [parameters.options.namespace] Project namespace
* @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
*/
module.exports = async function({workspace, options}) {
if (!options.namespace) {
log.warn(`Skipping bootstrap transformation due to missing namespace of project "${options.projectName}".`);
return;
let indexPath;
if (options.namespace) {
indexPath = `/resources/${options.namespace}/index.html`;
} else {
indexPath = "/index.html";
}
const resource = await workspace.byPath(`/resources/${options.namespace}/index.html`);
const resource = await workspace.byPath(indexPath);
if (!resource) {
log.warn(`Skipping bootstrap transformation due to missing index.html in project "${options.projectName}".`);
return;
Expand Down
5 changes: 3 additions & 2 deletions lib/types/AbstractBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class AbstractBuilder {

this.tasks = {};
this.taskExecutionOrder = [];
this.addStandardTasks({resourceCollections, project});
this.addStandardTasks({resourceCollections, project, log: this.log});
this.addCustomTasks({resourceCollections, project});
}

Expand All @@ -40,8 +40,9 @@ class AbstractBuilder {
* @param {module:@ui5/fs.DuplexCollection} parameters.resourceCollections.workspace Workspace Resource
* @param {ReaderCollection} parameters.resourceCollections.dependencies Workspace Resource
* @param {Object} parameters.project Project configuration
* @param {Object} parameters.log <code>@ui5/logger</code> logger instance
*/
addStandardTasks({resourceCollections, project}) {
addStandardTasks({resourceCollections, project, log}) {
throw new Error("Function 'addStandardTasks' is not implemented");
}

Expand Down
11 changes: 9 additions & 2 deletions lib/types/application/ApplicationBuilder.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const AbstractBuilder = require("../AbstractBuilder");

const tasks = { // can't require index.js due to circular dependency
generateComponentPreload: require("../../tasks/bundlers/generateComponentPreload"),
generateFlexChangesBundle: require("../../tasks/bundlers/generateFlexChangesBundle"),
Expand All @@ -16,7 +17,13 @@ const tasks = { // can't require index.js due to circular dependency
};

class ApplicationBuilder extends AbstractBuilder {
addStandardTasks({resourceCollections, project}) {
addStandardTasks({resourceCollections, project, log}) {
if (!project.metadata.namespace) {
log.info("Skipping some tasks due to missing application namespace information. If your project contains" +
"a Component.js, you might be missing a manifest.json file. " +
"Also see: https://github.com/SAP/ui5-builder#application");
}

this.addTask("replaceCopyright", () => {
return tasks.replaceCopyright({
workspace: resourceCollections.workspace,
Expand Down Expand Up @@ -70,7 +77,7 @@ class ApplicationBuilder extends AbstractBuilder {
}
});
});
} else {
} else if (project.metadata.namespace) {
// Default component preload for application namespace
this.addTask("generateComponentPreload", async () => {
return tasks.generateComponentPreload({
Expand Down
10 changes: 3 additions & 7 deletions lib/types/application/ApplicationFormatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,13 @@ class ApplicationFormatter extends AbstractFormatter {
};

return this.readManifest(project).then(function(manifest) {
if (!manifest["sap.app"]) {
log.verbose("No 'sap.app' configuration found in manifest of project %s", project.metadata.name);
return;
}
if (!manifest["sap.app"].id) {
log.verbose("No application id found in manifest of project %s", project.metadata.name);
if (!manifest["sap.app"] || !manifest["sap.app"].id) {
log.warn(`No "sap.app" ID configuration found in manifest of project ${project.metadata.name}`, );
return;
}
project.metadata.namespace = manifest["sap.app"].id.replace(/\./g, "/");
}).catch((err) => {
log.verbose(`No manifest found for project ${project.metadata.name}. This might be an error in the future!`);
log.verbose(`No manifest found for project ${project.metadata.name}.`);
});
});
}
Expand Down
52 changes: 32 additions & 20 deletions test/lib/tasks/transformBootstrapHtml.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ test.serial("Transforms index.html resource", async (t) => {
const workspace = {
byPath: (actualPath) => {
t.deepEqual(actualPath, "/resources/sap/ui/demo/app/index.html",
"Reads index.html file from applicaiton namespace.");
"Reads index.html file from application namespace.");
return Promise.resolve(resource);
},
write: (actualResource) => {
Expand Down Expand Up @@ -68,42 +68,53 @@ test.serial("Transforms index.html resource", async (t) => {
t.true(t.context.logWarnSpy.notCalled, "No warnings should be logged");
});

test.serial("No index.html resource exists", async (t) => {
t.plan(4);
test.serial("Transforms index.html resource without namespace", async (t) => {
t.plan(5);

const resource = {};

const workspace = {
byPath: (actualPath) => {
t.deepEqual(actualPath, "/resources/sap/ui/demo/app/index.html",
"Reads index.html file from applicaiton namespace.");
return Promise.resolve(null);
t.deepEqual(actualPath, "/index.html",
"Reads index.html file from application namespace.");
return Promise.resolve(resource);
},
write: () => {
t.fail("No resources should be written to workspace");
write: (actualResource) => {
t.deepEqual(actualResource, resource,
"Expected resource is written back to workspace");
}
};

t.context.bootstrapHtmlTransformerStub.returns([resource]);

await transformBootstrapHtml({
workspace,
options: {
projectName: "sap.ui.demo.app",
namespace: "sap/ui/demo/app"
projectName: "sap.ui.demo.app"
}
});

t.true(t.context.bootstrapHtmlTransformerStub.notCalled,
"Processor should not be called");
t.deepEqual(t.context.bootstrapHtmlTransformerStub.callCount, 1,
"Processor should be called once");

t.deepEqual(t.context.logWarnSpy.callCount, 1, "One warning should be logged");
t.true(t.context.logWarnSpy.calledWith(`Skipping bootstrap transformation due to missing index.html in project "sap.ui.demo.app".`),
"Warning about missing index.html file should be logged");
t.true(t.context.bootstrapHtmlTransformerStub.calledWithExactly({
resources: [resource],
options: {
src: "resources/sap-ui-custom.js"
}
}), "Processor should be called with expected arguments");

t.true(t.context.logWarnSpy.notCalled, "No warnings should be logged");
});

test.serial("No namespace provided", async (t) => {
t.plan(3);
test.serial("No index.html resource exists", async (t) => {
t.plan(4);

const workspace = {
byPath: (actualPath) => {
t.fail("No index.html file should be read from workspace");
t.deepEqual(actualPath, "/resources/sap/ui/demo/app/index.html",
"Reads index.html file from application namespace.");
return Promise.resolve(null);
},
write: () => {
t.fail("No resources should be written to workspace");
Expand All @@ -113,14 +124,15 @@ test.serial("No namespace provided", async (t) => {
await transformBootstrapHtml({
workspace,
options: {
projectName: "sap.ui.demo.app"
projectName: "sap.ui.demo.app",
namespace: "sap/ui/demo/app"
}
});

t.true(t.context.bootstrapHtmlTransformerStub.notCalled,
"Processor should not be called");

t.deepEqual(t.context.logWarnSpy.callCount, 1, "One warning should be logged");
t.true(t.context.logWarnSpy.calledWith(`Skipping bootstrap transformation due to missing namespace of project "sap.ui.demo.app".`),
t.true(t.context.logWarnSpy.calledWith(`Skipping bootstrap transformation due to missing index.html in project "sap.ui.demo.app".`),
"Warning about missing index.html file should be logged");
});

0 comments on commit 184fe52

Please sign in to comment.