diff --git a/lib/builder/builder.js b/lib/builder/builder.js index cec101faa..56019a52d 100644 --- a/lib/builder/builder.js +++ b/lib/builder/builder.js @@ -16,14 +16,6 @@ const definedTasks = { generateLibraryPreload: require("../tasks/bundlers/generateLibraryPreload") }; - -// Set of tasks for development -const devTasks = [ - "replaceCopyright", - "replaceVersion", - "buildThemes" -]; - /** * Calculates the elapsed build time and returns a prettified output * @@ -50,29 +42,31 @@ function getElapsedTime(startTime) { * @param {Object} parameters.tree Dependency tree * @param {string} parameters.destPath Target path * @param {boolean} [parameters.buildDependencies=false] Decides whether project dependencies are built as well - * @param {boolean} [parameters.dev=false] Decides whether a development build should be activated (skips non-essential and time-intensive tasks) + * @param {boolean} [parameters.basic=false] Decides whether a basic build should be activated (skips non-essential and time-intensive tasks) * @param {boolean} [parameters.selfContained=false] Flag to activate self contained build * @param {Array} [parameters.includedTasks=[]] List of tasks to be included * @param {Array} [parameters.excludedTasks=[]] List of tasks to be excluced. If the wildcard '*' is provided, only the included tasks will be executed. - * @param {Array} [parameters.devExcludeProject=[]] List of projects to be exlcuded from development build + * @param {Array} [parameters.devExcludeProject=[]] List of projects to be exlcuded from basic build * @returns {Promise} Promise resolving to undefined once build has finished */ -function build({tree, destPath, buildDependencies = false, dev = false, selfContained = false, includedTasks = [], excludedTasks = [], devExcludeProject = []}) { +function build({tree, destPath, buildDependencies = false, basic = false, selfContained = false, includedTasks = [], excludedTasks = [], devExcludeProject = []}) { const startTime = process.hrtime(); log.info(`Building project ${tree.metadata.name}` + (buildDependencies ? "" : " not") + - " including dependencies..." + (dev ? " [dev mode]" : "")); + " including dependencies..." + (basic ? " [basic mode]" : "")); log.verbose(`Building to ${destPath}...`); - let selectedTasks = composeTaskList({dev, selfContained, includedTasks, excludedTasks}); - const fsTarget = resourceFactory.createAdapter({ fsBasePath: destPath, virBasePath: "/" }); + // If both build options are set to true, only the basic build mode will be activated + if (basic && selfContained) { + log.info("Building project in basic mode."); + selfContained = false; + } const projects = {}; // Unique project index to prevent building the same project multiple times - const projectCountMarker = {}; function projectCount(project, count = 0) { if (buildDependencies) { @@ -91,7 +85,6 @@ function build({tree, destPath, buildDependencies = false, dev = false, selfCont function buildProject(project) { let depPromise; - let projectTasks = selectedTasks; if (buildDependencies) { // Build dependencies in sequence as it is far easier to detect issues and reduces // side effects or other issues such as too many open files @@ -120,17 +113,17 @@ function build({tree, destPath, buildDependencies = false, dev = false, selfCont name: project.metadata.name }); - if (dev && devExcludeProject.indexOf(project.metadata.name) !== -1) { - projectTasks = composeTaskList({dev: false, selfContained, includedTasks, excludedTasks}); - } return projectType.build({ resourceCollections: { workspace, dependencies: resourceCollections.dependencies }, - tasks: projectTasks, project, - parentLogger: log + parentLogger: log, + buildOptions: { + basic: basic, + selfContained: selfContained + } }).then(() => { log.verbose("Finished building project %s. Writing out files...", project.metadata.name); buildLogger.completeWork(1); @@ -156,80 +149,6 @@ function build({tree, destPath, buildDependencies = false, dev = false, selfCont throw err; }); } -/** - * Creates the list of tasks to be executed by the build process - * - * Sets specific tasks to be disabled by default, these tasks need to be included explicitly. - * Based on the selected build mode (dev|selfContained|preload), different tasks are enabled. - * Tasks can be enabled or disabled. The wildcard * is also supported and affects all tasks. - * - * @private - * @param {boolean} dev Sets development mode, which only runs essential tasks - * @param {boolean} selfContained True if a the build should be self-contained or false for prelead build bundles - * @param {Array} includedTasks Task list to be included from build - * @param {Array} excludedTasks Task list to be exlcuded from build - * @returns {Array} Return a task list for the builder - */ -function composeTaskList({dev, selfContained, includedTasks, excludedTasks}) { - let selectedTasks = Object.keys(definedTasks).reduce((list, key) => { - list[key] = true; - return list; - }, {}); - - // Exclude tasks: manifestBundler - selectedTasks.generateManifestBundle = false; - selectedTasks.generateStandaloneAppBundle = false; - - if (selfContained) { - // No preloads, bundle only - selectedTasks.generateAppPreload = false; - selectedTasks.generateStandaloneAppBundle = true; - selectedTasks.generateLibraryPreload = false; - } - - // Only run essential tasks in development mode, it is not desired to run time consuming tasks during development. - if (dev) { - // Overwrite all other tasks with noop promise - Object.keys(selectedTasks).forEach((key) => { - if (devTasks.indexOf(key) === -1) { - selectedTasks[key] = false; - } - }); - } - - // Exclude tasks - for (let i = 0; i < excludedTasks.length; i++) { - let taskName = excludedTasks[i]; - if (taskName === "*") { - Object.keys(selectedTasks).forEach((sKey) => { - selectedTasks[sKey] = false; - }); - break; - } - if (selectedTasks[taskName] !== false) { - selectedTasks[taskName] = false; - } - } - - // Include tasks - for (let i = 0; i < includedTasks.length; i++) { - let taskName = includedTasks[i]; - if (taskName === "*") { - Object.keys(selectedTasks).forEach((sKey) => { - selectedTasks[sKey] = true; - }); - break; - } - if (selectedTasks[taskName] === false) { - selectedTasks[taskName] = true; - } - } - - // Filter only for tasks that will be executed - selectedTasks = Object.keys(selectedTasks).filter((task) => selectedTasks[task]); - - return selectedTasks; -} module.exports = { build: build, diff --git a/lib/types/AbstractBuilder.js b/lib/types/AbstractBuilder.js index e8ed559b7..d42044e14 100644 --- a/lib/types/AbstractBuilder.js +++ b/lib/types/AbstractBuilder.js @@ -10,27 +10,39 @@ class AbstractBuilder { * @param {object} project Project configuration * @param {GroupLogger} parentLogger Logger to use */ - constructor({project, parentLogger}) { - this.tasks = {}; + constructor({resourceCollections, project, parentLogger, buildOptions}) { + this.resourceCollections = resourceCollections; + this.project = project; + this.buildOptions = buildOptions; + this.tasks = []; this.log = parentLogger.createSubLogger(project.type + " " + project.metadata.name, 0.2); this.taskLog = this.log.createTaskLogger("🔨"); - this.availableTasks = []; } + // Build Phases + preprocess() {} + + themebuilding() {} + + process() {} + + bundle() {} + + optimize() {} + /** * Adds a executable task to the builder * - * This does not ensure the correct build order. The order is maintained through the property - * [availableTasks]{@link AbstractBuilder#availableTasks} + * The build order depends on the order the build tasks get added to the project. * - * @param {string} taskName Name of the task which should be in the list availableTasks. + * @param {string} taskName Name of the task * @param {function} taskFunction */ addTask(taskName, taskFunction) { - if (this.availableTasks.indexOf(taskName) === -1) { - throw new Error(`Task "${taskName}" does not exist.`); - } - this.tasks[taskName] = taskFunction; + this.tasks.push({ + taskName: taskName, + taskFunction: taskFunction + }); } /** @@ -39,20 +51,27 @@ class AbstractBuilder { * @param {array} tasksToRun List of tasks which should be executed * @returns {Promise} Returns promise chain with tasks */ - build(tasksToRun) { - const allTasksCount = tasksToRun.filter((value) => this.availableTasks.includes(value)).length; + build() { + // Run phases and add tasks to queue + this.preprocess(); + this.themebuilding(); + + if (!this.buildOptions.basic) { + this.process(); + this.bundle(); + this.optimize(); + } + + const allTasksCount = Object.keys(this.tasks).length; this.taskLog.addWork(allTasksCount); let taskChain = Promise.resolve(); - for (let i = 0; i < this.availableTasks.length; i++) { - let taskName = this.availableTasks[i]; - - if (!tasksToRun.includes(taskName)) { - continue; - } - taskChain = taskChain.then(this.wrapTask(taskName, this.tasks[taskName])); + for (var i = 0; i < this.tasks.length; i++) { + var task = this.tasks[i]; + taskChain = taskChain.then(this.wrapTask(task.taskName, task.taskFunction)); } + return taskChain; } diff --git a/lib/types/application/ApplicationBuilder.js b/lib/types/application/ApplicationBuilder.js index 2acc77fb1..acb25395f 100644 --- a/lib/types/application/ApplicationBuilder.js +++ b/lib/types/application/ApplicationBuilder.js @@ -2,10 +2,8 @@ const AbstractBuilder = require("../AbstractBuilder"); const tasks = { // can't require index.js due to circular dependency generateAppPreload: require("../../tasks/bundlers/generateAppPreload"), generateFlexChangesBundle: require("../../tasks/bundlers/generateFlexChangesBundle"), - generateLibraryPreload: require("../../tasks/bundlers/generateLibraryPreload"), generateManifestBundle: require("../../tasks/bundlers/generateManifestBundle"), generateStandaloneAppBundle: require("../../tasks/bundlers/generateStandaloneAppBundle"), - buildThemes: require("../../tasks/buildThemes"), createDebugFiles: require("../../tasks/createDebugFiles"), generateVersionInfo: require("../../tasks/generateVersionInfo"), replaceCopyright: require("../../tasks/replaceCopyright"), @@ -14,28 +12,16 @@ const tasks = { // can't require index.js due to circular dependency }; class ApplicationBuilder extends AbstractBuilder { - constructor({resourceCollections, project, parentLogger}) { - super({project, parentLogger}); - - // All available library tasks in execution order - this.availableTasks = [ - "replaceCopyright", - "replaceVersion", - "createDebugFiles", - "generateFlexChangesBundle", - "generateManifestBundle", - "generateAppPreload", - "generateStandaloneAppBundle", - "uglify", - "generateVersionInfo" - ]; - + /** + * Adds tasks for the copyright header and version replacement of the project. + */ + preprocess() { this.addTask("replaceCopyright", () => { const replaceCopyright = tasks.replaceCopyright; return replaceCopyright({ - workspace: resourceCollections.workspace, + workspace: this.resourceCollections.workspace, options: { - copyright: project.metadata.copyright, + copyright: this.project.metadata.copyright, pattern: "/**/*.js" } }); @@ -44,30 +30,40 @@ class ApplicationBuilder extends AbstractBuilder { this.addTask("replaceVersion", () => { const replaceVersion = tasks.replaceVersion; return replaceVersion({ - workspace: resourceCollections.workspace, + workspace: this.resourceCollections.workspace, options: { - version: project.version, + version: this.project.version, pattern: "/**/*.js" } }); }); + } + /** + * Adds tasks for debug files creation of the project. + */ + process() { this.addTask("createDebugFiles", () => { const createDebugFiles = tasks.createDebugFiles; return createDebugFiles({ - workspace: resourceCollections.workspace, + workspace: this.resourceCollections.workspace, options: { pattern: "/**/*.js" } }); }); + } + /** + * Adds tasks for the bundling of the project + */ + bundle() { this.addTask("generateFlexChangesBundle", () => { const generateFlexChangesBundle = tasks.generateFlexChangesBundle; return generateFlexChangesBundle({ - workspace: resourceCollections.workspace, + workspace: this.resourceCollections.workspace, options: { - namespace: project.metadata.namespace + namespace: this.project.metadata.namespace } }); }); @@ -75,39 +71,46 @@ class ApplicationBuilder extends AbstractBuilder { this.addTask("generateManifestBundle", () => { const generateManifestBundle = tasks.generateManifestBundle; return generateManifestBundle({ - workspace: resourceCollections.workspace, + workspace: this.resourceCollections.workspace, options: { - namespace: project.metadata.namespace + namespace: this.project.metadata.namespace } }); }); - this.addTask("generateAppPreload", () => { - const generateAppPreload = tasks.generateAppPreload; - return generateAppPreload({ - workspace: resourceCollections.workspace, - dependencies: resourceCollections.dependencies, - options: { - namespace: project.metadata.namespace - } + if (this.buildOptions.selfContained) { + this.addTask("generateStandaloneAppBundle", () => { + const generateStandaloneAppBundle = tasks.generateStandaloneAppBundle; + return generateStandaloneAppBundle({ + workspace: this.resourceCollections.workspace, + dependencies: this.resourceCollections.dependencies, + options: { + namespace: this.project.metadata.namespace + } + }); }); - }); - - this.addTask("generateStandaloneAppBundle", () => { - const generateStandaloneAppBundle = tasks.generateStandaloneAppBundle; - return generateStandaloneAppBundle({ - workspace: resourceCollections.workspace, - dependencies: resourceCollections.dependencies, - options: { - namespace: project.metadata.namespace - } + } else { + this.addTask("generateAppPreload", () => { + const generateAppPreload = tasks.generateAppPreload; + return generateAppPreload({ + workspace: this.resourceCollections.workspace, + dependencies: this.resourceCollections.dependencies, + options: { + namespace: this.project.metadata.namespace + } + }); }); - }); + } + } + /** + * Adds tasks for the uglifying and version info generating of the project. + */ + optimize() { this.addTask("uglify", () => { const uglify = tasks.uglify; return uglify({ - workspace: resourceCollections.workspace, + workspace: this.resourceCollections.workspace, options: { pattern: "/**/*.js" } @@ -117,10 +120,10 @@ class ApplicationBuilder extends AbstractBuilder { this.addTask("generateVersionInfo", () => { const generateVersionInfo = tasks.generateVersionInfo; return generateVersionInfo({ - workspace: resourceCollections.workspace, - dependencies: resourceCollections.dependencies, + workspace: this.resourceCollections.workspace, + dependencies: this.resourceCollections.dependencies, options: { - rootProject: project, + rootProject: this.project, pattern: "/**/.library" } }); diff --git a/lib/types/application/applicationType.js b/lib/types/application/applicationType.js index b6e85b83b..5fe07ab8f 100644 --- a/lib/types/application/applicationType.js +++ b/lib/types/application/applicationType.js @@ -5,8 +5,8 @@ module.exports = { format: function(project) { return new ApplicationFormatter().format(project); }, - build: function({resourceCollections, tasks, project, parentLogger}) { - return new ApplicationBuilder({resourceCollections, project, parentLogger}).build(tasks); + build: function({resourceCollections, project, parentLogger, buildOptions}) { + return new ApplicationBuilder({resourceCollections, project, parentLogger, buildOptions}).build(); }, // Export type classes for extensibility diff --git a/lib/types/library/LibraryBuilder.js b/lib/types/library/LibraryBuilder.js index 0fdcc1f0c..db822022c 100644 --- a/lib/types/library/LibraryBuilder.js +++ b/lib/types/library/LibraryBuilder.js @@ -1,38 +1,21 @@ const AbstractBuilder = require("../AbstractBuilder"); const tasks = { // can't require index.js due to circular dependency - generateAppPreload: require("../../tasks/bundlers/generateAppPreload"), - generateFlexChangesBundle: require("../../tasks/bundlers/generateFlexChangesBundle"), generateLibraryPreload: require("../../tasks/bundlers/generateLibraryPreload"), - generateManifestBundle: require("../../tasks/bundlers/generateManifestBundle"), - generateStandaloneAppBundle: require("../../tasks/bundlers/generateStandaloneAppBundle"), buildThemes: require("../../tasks/buildThemes"), createDebugFiles: require("../../tasks/createDebugFiles"), - generateVersionInfo: require("../../tasks/generateVersionInfo"), replaceCopyright: require("../../tasks/replaceCopyright"), replaceVersion: require("../../tasks/replaceVersion"), uglify: require("../../tasks/uglify") }; class LibraryBuilder extends AbstractBuilder { - constructor({resourceCollections, project, parentLogger}) { - super({project, parentLogger}); - - // All availble library tasks in execution order - this.availableTasks = [ - "replaceCopyright", - "replaceVersion", - "buildThemes", - "generateLibraryPreload", - "createDebugFiles", - "uglify", - ]; - + preprocess() { this.addTask("replaceCopyright", () => { const replaceCopyright = tasks.replaceCopyright; return replaceCopyright({ - workspace: resourceCollections.workspace, + workspace: this.resourceCollections.workspace, options: { - copyright: project.metadata.copyright, + copyright: this.project.metadata.copyright, pattern: "/resources/**/*.js" } }); @@ -41,52 +24,60 @@ class LibraryBuilder extends AbstractBuilder { this.addTask("replaceVersion", () => { const replaceVersion = tasks.replaceVersion; return replaceVersion({ - workspace: resourceCollections.workspace, + workspace: this.resourceCollections.workspace, options: { - version: project.version, + version: this.project.version, pattern: "/resources/**/*.js" } }); }); + } + themebuilding() { this.addTask("buildThemes", () => { const buildThemes = tasks.buildThemes; return buildThemes({ - workspace: resourceCollections.workspace, - dependencies: resourceCollections.dependencies, + workspace: this.resourceCollections.workspace, + dependencies: this.resourceCollections.dependencies, options: { - projectName: project.metadata.name, + projectName: this.project.metadata.name, librariesPattern: "/resources/**/*.library", inputPattern: "/resources/**/themes/*/library.source.less" } }); }); + } - this.addTask("generateLibraryPreload", () => { - const generateLibraryPreload = tasks.generateLibraryPreload; - return generateLibraryPreload({ - workspace: resourceCollections.workspace, - dependencies: resourceCollections.dependencies, + process() { + this.addTask("createDebugFiles", () => { + const createDebugFiles = tasks.createDebugFiles; + return createDebugFiles({ + workspace: this.resourceCollections.workspace, options: { - projectName: project.metadata.name + pattern: "/resources/**/*.js" } }); }); + } - this.addTask("createDebugFiles", () => { - const createDebugFiles = tasks.createDebugFiles; - return createDebugFiles({ - workspace: resourceCollections.workspace, + bundle() { + this.addTask("generateLibraryPreload", () => { + const generateLibraryPreload = tasks.generateLibraryPreload; + return generateLibraryPreload({ + workspace: this.resourceCollections.workspace, + dependencies: this.resourceCollections.dependencies, options: { - pattern: "/resources/**/*.js" + projectName: this.project.metadata.name } }); }); + } + optimize() { this.addTask("uglify", () => { const uglify = tasks.uglify; return uglify({ - workspace: resourceCollections.workspace, + workspace: this.resourceCollections.workspace, options: { pattern: "/resources/**/*.js" } diff --git a/lib/types/library/libraryType.js b/lib/types/library/libraryType.js index 222034322..25badeb9e 100644 --- a/lib/types/library/libraryType.js +++ b/lib/types/library/libraryType.js @@ -5,8 +5,8 @@ module.exports = { format: function(project) { return new LibraryFormatter().format(project); }, - build: function({resourceCollections, tasks, project, parentLogger}) { - return new LibraryBuilder({resourceCollections, project, parentLogger}).build(tasks); + build: function({resourceCollections, project, parentLogger, buildOptions}) { + return new LibraryBuilder({resourceCollections, project, parentLogger, buildOptions}).build(); }, // Export type classes for extensibility diff --git a/test/expected/build/library.d/dest/resources/library/d/library-preload.js b/test/expected/build/library.d/dest/resources/library/d/library-preload.js new file mode 100644 index 000000000..f6c60673c --- /dev/null +++ b/test/expected/build/library.d/dest/resources/library/d/library-preload.js @@ -0,0 +1,14 @@ +jQuery.sap.registerPreloadedModules({ +"version":"2.0", +"modules":{ + "library/d/some-dbg.js":function(){/*! + * Some fancy copyright + */ +console.log("HelloWorld"); +}, + "library/d/some.js":function(){/*! + * Some fancy copyright + */ +console.log("HelloWorld"); +} +}}); diff --git a/test/expected/build/library.e/dest/resources/library/e/library-preload.js b/test/expected/build/library.e/dest/resources/library/e/library-preload.js new file mode 100644 index 000000000..5d27a7f6a --- /dev/null +++ b/test/expected/build/library.e/dest/resources/library/e/library-preload.js @@ -0,0 +1,18 @@ +jQuery.sap.registerPreloadedModules({ +"version":"2.0", +"modules":{ + "library/e/some-dbg.js":function(){/*! + * UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ +console.log("HelloWorld"); +}, + "library/e/some.js":function(){/*! + * UI development toolkit for HTML5 (OpenUI5) + * (c) Copyright 2009-xxx SAP SE or an SAP affiliate company. + * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. + */ +console.log("HelloWorld"); +} +}}); diff --git a/test/lib/builder/builder.js b/test/lib/builder/builder.js index c66e73a15..cf2127529 100644 --- a/test/lib/builder/builder.js +++ b/test/lib/builder/builder.js @@ -24,31 +24,6 @@ const findFiles = (folder) => { }); }; -test("Build application.a", (t) => { - const destPath = "./test/tmp/build/application.a/dest"; - const expectedPath = "./test/expected/build/application.a/dest"; - - return builder.build({ - tree: applicationATree, - destPath, - excludedTasks: ["generateAppPreload", "generateStandaloneAppBundle", "generateVersionInfo"] - }).then(() => { - return findFiles(expectedPath); - }).then((expectedFiles) => { - console.log(expectedFiles); - // Check for all directories and files - assert.directoryDeepEqual(destPath, expectedPath); - console.log("after assert"); - // Check for all file contents - expectedFiles.forEach((expectedFile) => { - const relativeFile = path.relative(expectedPath, expectedFile); - const destFile = path.join(destPath, relativeFile); - assert.fileEqual(destFile, expectedFile); - }); - t.pass(); - }); -}); - test("Build application.a [dev mode]", (t) => { const destPath = "./test/tmp/build/application.a/dest-dev"; const expectedPath = "./test/expected/build/application.a/dest-dev"; @@ -56,7 +31,7 @@ test("Build application.a [dev mode]", (t) => { return builder.build({ tree: applicationATree, destPath, - dev: true + basic: true }).then(() => { return findFiles(expectedPath); }).then((expectedFiles) => { @@ -79,8 +54,7 @@ test("Build library.d with copyright from .library file", (t) => { return builder.build({ tree: libraryDTree, - destPath, - excludedTasks: ["generateLibraryPreload"] + destPath }).then(() => { return findFiles(expectedPath); }).then((expectedFiles) => { @@ -103,8 +77,7 @@ test("Build library.e with copyright from settings of ui5.yaml", (t) => { return builder.build({ tree: libraryETree, - destPath, - excludedTasks: ["generateLibraryPreload"] + destPath }).then(() => { return findFiles(expectedPath); }).then((expectedFiles) => {