Skip to content

Commit

Permalink
perf: improve incremental build times
Browse files Browse the repository at this point in the history
  • Loading branch information
janvennemann committed Mar 27, 2019
1 parent 42cc21e commit df66a18
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 81 deletions.
123 changes: 42 additions & 81 deletions Alloy/commands/compile/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ var ejs = require('ejs'),
BuildLog = require('./BuildLog'),
Orphanage = require('./Orphanage');

const MvcCompileTask = require('./tasks/mvc-compile-task');

var alloyRoot = path.join(__dirname, '..', '..'),
viewRegex = new RegExp('\\.' + CONST.FILE_EXT.VIEW + '$'),
controllerRegex = new RegExp('\\.' + CONST.FILE_EXT.CONTROLLER + '$'),
Expand Down Expand Up @@ -418,93 +420,52 @@ module.exports = function(args, program) {
CU.models.push(m);
});

// Create a regex for determining which platform-specific
// folders should be used in the compile process
var filteredPlatforms = _.reject(CONST.PLATFORM_FOLDERS_ALLOY, function(p) {
return p === buildPlatform;
});
filteredPlatforms = _.map(filteredPlatforms, function(p) { return p + '[\\\\\\/]'; });
var filterRegex = new RegExp('^(?:(?!' + filteredPlatforms.join('|') + '))');

// don't process XML/controller files inside .svn folders (ALOY-839)
var excludeRegex = new RegExp('(?:^|[\\/\\\\])(?:' + CONST.EXCLUDED_FILES.join('|') + ')(?:$|[\\/\\\\])');

// Process all views/controllers and generate their runtime
// commonjs modules and source maps.
var tracker = {};
_.each(widgetDirs, function(collection) {
// generate runtime controllers from views
var theViewDir = path.join(collection.dir, CONST.DIR.VIEW);
if (fs.existsSync(theViewDir)) {
_.each(walkSync(theViewDir), function(view) {
view = path.normalize(view);
if (viewRegex.test(view) && filterRegex.test(view) && !excludeRegex.test(view)) {
// make sure this controller is only generated once
var theFile = view.substring(0, view.lastIndexOf('.'));
var theKey = theFile.replace(new RegExp('^' + buildPlatform + '[\\/\\\\]'), '');
var fp = path.join(collection.dir, theKey);
if (tracker[fp]) { return; }

// generate runtime controller
logger.info('[' + view + '] ' + (collection.manifest ? collection.manifest.id +
' ' : '') + 'view processing...');
parseAlloyComponent(view, collection.dir, collection.manifest, null, restrictionPath);
tracker[fp] = true;
}
});
}

// generate runtime controllers from any controller code that has no
// corresponding view markup
var theControllerDir = path.join(collection.dir, CONST.DIR.CONTROLLER);
if (fs.existsSync(theControllerDir)) {
_.each(walkSync(theControllerDir), function(controller) {
controller = path.normalize(controller);
if (controllerRegex.test(controller) && filterRegex.test(controller) && !excludeRegex.test(controller)) {
// make sure this controller is only generated once
var theFile = controller.substring(0, controller.lastIndexOf('.'));
var theKey = theFile.replace(new RegExp('^' + buildPlatform + '[\\/\\\\]'), '');
var fp = path.join(collection.dir, theKey);
if (tracker[fp]) { return; }

// generate runtime controller
logger.info('[' + controller + '] ' + (collection.manifest ?
collection.manifest.id + ' ' : '') + 'controller processing...');
parseAlloyComponent(controller, collection.dir, collection.manifest, true, restrictionPath);
tracker[fp] = true;
}
return Promise.resolve()
.then(() => {
const mvcCompileTask = new MvcCompileTask({
incrementalDirectory: path.join(compileConfig.dir.project, 'build', 'alloy', 'incremental', 'mvc'),
logger,
compileConfig,
restrictionPath,
parseAlloyComponent,
sourceCollections: widgetDirs,
targetPlatform: buildPlatform
});
}
});
logger.info('');

generateAppJs(paths, compileConfig, restrictionPath, compilerMakeFile);

// ALOY-905: workaround TiSDK < 3.2.0 iOS device build bug where it can't reference app.js
// in platform-specific folders, so we just copy the platform-specific one to
// the Resources folder.
if (buildPlatform === 'ios' && tiapp.version.lt('3.2.0')) {
U.copyFileSync(path.join(paths.resources, titaniumFolder, 'app.js'), path.join(paths.resources, 'app.js'));
}
return mvcCompileTask.run();
})
.then(() => {
generateAppJs(paths, compileConfig, restrictionPath, compilerMakeFile);

// ALOY-905: workaround TiSDK < 3.2.0 iOS device build bug where it can't reference app.js
// in platform-specific folders, so we just copy the platform-specific one to
// the Resources folder.
if (buildPlatform === 'ios' && tiapp.version.lt('3.2.0')) {
U.copyFileSync(path.join(paths.resources, titaniumFolder, 'app.js'), path.join(paths.resources, 'app.js'));
}

// optimize code
logger.info('----- OPTIMIZING -----');
// optimize code
logger.info('----- OPTIMIZING -----');

if (restrictionSkipOptimize) {
logger.info('Skipping optimize due to file restriction.');
} else {
optimizeCompiledCode(alloyConfig, paths);
}
if (restrictionSkipOptimize) {
logger.info('Skipping optimize due to file restriction.');
} else {
optimizeCompiledCode(alloyConfig, paths);
}

// trigger our custom compiler makefile
if (compilerMakeFile.isActive) {
compilerMakeFile.trigger('post:compile', _.clone(compileConfig));
}
// trigger our custom compiler makefile
if (compilerMakeFile.isActive) {
compilerMakeFile.trigger('post:compile', _.clone(compileConfig));
}

// write out the log for this build
buildLog.write();
// write out the log for this build
buildLog.write();

BENCHMARK('TOTAL', true);
BENCHMARK('TOTAL', true);
})
.catch(e => {
logger.error(e);
U.die(e.message);
});
};


Expand Down
144 changes: 144 additions & 0 deletions Alloy/commands/compile/tasks/mvc-compile-task.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
const _ = require('lodash');
const { IncrementalFileTask } = require('appc-tasks');
const fs = require('fs');
const path = require('path');
const walkSync = require('walk-sync');

const CONST = require('alloy/Alloy/common/constants');

const viewRegex = new RegExp('\\.' + CONST.FILE_EXT.VIEW + '$');
const controllerRegex = new RegExp('\\.' + CONST.FILE_EXT.CONTROLLER + '$');
const excludeRegex = new RegExp('(?:^|[\\/\\\\])(?:' + CONST.EXCLUDED_FILES.join('|') + ')(?:$|[\\/\\\\])');

/**
* Task to compile controllers/views and their styles
*/
class MvcCompileTask extends IncrementalFileTask {
/**
* Constructs a new MVC compile task.
*
* @param {Object} options Configuration object for this task
*/
constructor(options) {
options.name = options.name || 'mvc-compile';
super(options);

this.targetPlatform = options.targetPlatform;
this.restrictionPath = options.restrictionPath;
this.parseAlloyComponent = options.parseAlloyComponent;

// Create a regex for determining which platform-specific
// folders should be used in the compile process
let filteredPlatforms = _.reject(CONST.PLATFORM_FOLDERS_ALLOY, p => {
return p === this.targetPlatform;
});
filteredPlatforms = _.map(filteredPlatforms, function(p) { return p + '[\\\\\\/]'; });
this.filterRegex = new RegExp('^(?:(?!' + filteredPlatforms.join('|') + '))');

this.processed = {};
this.rawSourceCollections = [];

if (options.sourceCollections) {
this.sourceCollections = options.sourceCollections;
}
}

set sourceCollections(sourceCollections) {
this.inputFiles = new Set();
this.rawSourceCollections = sourceCollections;
const candidateSourcePaths = [CONST.DIR.VIEW, CONST.DIR.CONTROLLER];
_.each(sourceCollections, collection => {
for (const candidateSourcePath of candidateSourcePaths) {
var sourcePath = path.join(collection.dir, candidateSourcePath);
if (!fs.existsSync(sourcePath)) {
continue;
}

_.each(walkSync(sourcePath), componentFilename => {
componentFilename = path.normalize(componentFilename);
const componentRegex = candidateSourcePath === CONST.DIR.VIEW ? viewRegex : controllerRegex;
if (componentRegex.test(componentFilename) && this.filterRegex.test(componentFilename) && !excludeRegex.test(componentFilename)) {
this.addInputFile(path.join(sourcePath, componentFilename));
}
});
}
});
}

/**
* Does a full task run, processing every input file.
*
* @return {Promise}
*/
doFullTaskRun() {
// @todo make this parallel?
for (const inputFile of this.inputFiles) {
this.compileAlloyComponent(inputFile);
}

return Promise.resolve();
}

/**
* Does an incremental task run, processing only changed files.
*
* @param {Map} changedFiles Map of file paths and their current file state (created, changed, deleted)
* @return {Promise}
*/
doIncrementalTaskRun(changedFiles) {
changedFiles.forEach((fileState, filePath) => {
if (fileState === 'deleted') {
// @todo delete associated controller, view and styles
} else {
const collection = this.findSourceCollection(inputFile);
const parseAsCotroller = controllerRegex.test(inputFile);
parseAlloyComponent(view, collection.dir, collection.manifest, parseAsCotroller, restrictionPath);
}
});

return Promise.resolve();
}

/**
* Finds the source collection a file part of.
*
* @param {String} filePath Full path to the file for which to look up the source collection.
*/
findSourceCollection(filePath) {
for (const collection of this.rawSourceCollections) {
if (filePath.startsWith(collection.dir)) {
return collection;
}
}

throw new Error(`Source collection not found for ${filePath}.`);
}

/**
* Compiles an Alloy component (controller/view).
*
* Note that this only needs to be done once for either the view OR the controller.
*
* @param {*} inputFile
*/
compileAlloyComponent(inputFile) {
const collection = this.findSourceCollection(inputFile);
const parseAsCotroller = controllerRegex.test(inputFile);
let relativeComponentPath = inputFile.replace(collection.dir, '');
relativeComponentPath = relativeComponentPath.replace(new RegExp(`^${path.sep}?(views|controllers)${path.sep}?`), '');

var relativeComponentPathWithoutExtension = relativeComponentPath.substring(0, relativeComponentPath.lastIndexOf('.'));
var componentIdentifier = relativeComponentPathWithoutExtension.replace(new RegExp('^' + this.targetPlatform + '[\\/\\\\]'), '');
var fullComponentIdentifier = path.join(collection.dir, componentIdentifier);
if (this.processed[fullComponentIdentifier]) {
return;
}

this.logger.info('[' + relativeComponentPath + '] ' + (collection.manifest ? collection.manifest.id +
' ' : '') + `${parseAsCotroller ? 'controller' : 'view'} processing...`);
this.parseAlloyComponent(relativeComponentPath, collection.dir, collection.manifest, parseAsCotroller, this.restrictionPath);
this.processed[fullComponentIdentifier] = true;
}
}

module.exports = MvcCompileTask;

0 comments on commit df66a18

Please sign in to comment.