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

Refactor Abstract Builder and introduce Build Phases #5

Closed
wants to merge 1 commit into from
Closed
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
109 changes: 14 additions & 95 deletions lib/builder/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -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<undefined>} Promise resolving to <code>undefined</code> 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) {
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand All @@ -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 <code>*</code> 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,
Expand Down
57 changes: 38 additions & 19 deletions lib/types/AbstractBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
}

/**
Expand All @@ -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();
Copy link
Member

Choose a reason for hiding this comment

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

Remove build phases until more use cases are known and naming is clear

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++) {
Copy link
Member

Choose a reason for hiding this comment

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

Move this into a separate function to allow easier overwrite of the build function

var task = this.tasks[i];
taskChain = taskChain.then(this.wrapTask(task.taskName, task.taskFunction));
}

return taskChain;
}

Expand Down
Loading