2015-02-24 11:35:32 +00:00
|
|
|
var NodeFsExtra = require('fs-extra');
|
|
|
|
var NodeMinify = require('minify');
|
|
|
|
var NodeEvents = require('events');
|
|
|
|
var NodeFetch = require('fetch');
|
|
|
|
var NodeFs = require('fs');
|
|
|
|
|
2015-02-24 14:42:32 +00:00
|
|
|
var DEFAULT_DOWNLOAD_MAX_SIZE = (1024*1024) * 10; // 10mb
|
|
|
|
var DEFAULT_DOWNLOAD_TIMEOUT = (1000*60) * 1; // 1min
|
|
|
|
var DEFAULT_OPTIONS = {
|
2015-02-24 11:35:32 +00:00
|
|
|
downloadStartDelay: null,
|
|
|
|
downloadForce: false,
|
|
|
|
downloadOptions: {
|
2015-02-24 14:42:32 +00:00
|
|
|
timeout: DEFAULT_DOWNLOAD_TIMEOUT,
|
|
|
|
maxResponseLength: DEFAULT_DOWNLOAD_MAX_SIZE,
|
2015-02-24 11:35:32 +00:00
|
|
|
agent: 'node-ff-assets asset fetcher'
|
|
|
|
},
|
|
|
|
|
|
|
|
linkTargetSingleResult: true,
|
|
|
|
linkTarget: null,
|
|
|
|
linkSources: [],
|
|
|
|
linkMapping: [],
|
|
|
|
|
|
|
|
assetHeader: '\n/* node-ff-assets: begin */\n\n',
|
|
|
|
assetFooter: '\n/* node-ff-assets: end */\n\n',
|
|
|
|
assetSeperator: '\n/* node-ff-assets: next */\n',
|
|
|
|
}
|
|
|
|
|
|
|
|
function ResourceBuilder(config, readFile) {
|
|
|
|
this.config = config;
|
|
|
|
this.startTime = 0;
|
|
|
|
this.readFile = readFile || FunctionFactory.Constructor.readFile();
|
|
|
|
NodeEvents.EventEmitter.call(this);
|
|
|
|
|
|
|
|
this.run = function(callback) {
|
|
|
|
configCheckSync(this.config);
|
|
|
|
configDefaultsSync(this.config);
|
|
|
|
runBuildSafe(this, this.readFile, callback);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
ResourceBuilder.prototype.__proto__ = NodeEvents.EventEmitter.prototype;
|
|
|
|
|
|
|
|
var FunctionFactory = {
|
|
|
|
Constructor: {
|
|
|
|
readFile: function() {
|
|
|
|
return function(file,callback) {
|
|
|
|
NodeFs.readFile(file, function(err, data) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
callback(null, data);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
},
|
|
|
|
readFileMinify: function(options) {
|
|
|
|
if (options == null) {
|
|
|
|
options = {};
|
|
|
|
}
|
|
|
|
return function(file, callback) {
|
|
|
|
NodeMinify(file, options, function(err, data) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
callback(null, data);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Event: {
|
|
|
|
log: {
|
|
|
|
console: function(logType) {
|
|
|
|
if (logType == null) {
|
|
|
|
throw Error('no logType');
|
|
|
|
}
|
|
|
|
return function(logLevel, logMessage) {
|
|
|
|
console.log(logLevel+': node-ff-assets-'+logType+' '+logMessage);
|
|
|
|
};
|
|
|
|
},
|
|
|
|
},
|
|
|
|
result: {
|
|
|
|
objectFunction: function(object, functionName, resultKey) {
|
|
|
|
if (object == null) {
|
|
|
|
throw Error('no object');
|
|
|
|
}
|
|
|
|
if (functionName == null) {
|
|
|
|
throw Error('no functionName');
|
|
|
|
}
|
|
|
|
return function(resultValue) {
|
|
|
|
if (resultKey == null) {
|
|
|
|
object[functionName](resultValue);
|
|
|
|
} else {
|
|
|
|
object[functionName](resultKey, resultValue);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
2015-02-24 14:42:32 +00:00
|
|
|
objectAdd: function(object,resultKey) {
|
|
|
|
return FunctionFactory.Event.result.objectFunction(object,'add',resultKey);
|
|
|
|
},
|
2015-02-24 11:35:32 +00:00
|
|
|
objectSet: function(object,resultKey) {
|
|
|
|
return FunctionFactory.Event.result.objectFunction(object,'set',resultKey);
|
|
|
|
},
|
|
|
|
objectPut: function(object,resultKey) {
|
|
|
|
return FunctionFactory.Event.result.objectFunction(object,'put',resultKey);
|
|
|
|
},
|
|
|
|
arrayPush: function(object,resultKey) {
|
|
|
|
return FunctionFactory.Event.result.objectFunction(object,'push');
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function configCheckSync(config) {
|
|
|
|
if (!config) {
|
|
|
|
throw Error('no config');
|
|
|
|
}
|
|
|
|
if (!config.linkTarget) {
|
|
|
|
throw Error('no config.linkTarget');
|
|
|
|
}
|
|
|
|
if (!config.linkSources) {
|
|
|
|
throw Error('no config.linkSources');
|
|
|
|
}
|
|
|
|
if (!config.linkMapping) {
|
|
|
|
throw Error('no config.linkMapping');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function configDefaultsSync(config) {
|
|
|
|
if (config.downloadOptions == null) {
|
|
|
|
config.downloadOptions = DEFAULT_OPTIONS.downloadOptions;
|
|
|
|
}
|
|
|
|
if (config.assetHeader == null) {
|
|
|
|
config.assetHeader = DEFAULT_OPTIONS.assetHeader;
|
|
|
|
}
|
|
|
|
if (config.assetFooter == null) {
|
|
|
|
config.assetFooter = DEFAULT_OPTIONS.assetFooter;
|
|
|
|
}
|
|
|
|
if (config.assetSeperator == null) {
|
|
|
|
config.assetSeperator = DEFAULT_OPTIONS.assetSeperator;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function runBuildSafe(builder, readFile, callback) {
|
|
|
|
runBuild(builder, readFile, function(err) {
|
|
|
|
// send error
|
|
|
|
if (err) {
|
|
|
|
builder.emit('error',err);
|
|
|
|
}
|
|
|
|
// finish callback
|
|
|
|
if (callback != null) {
|
|
|
|
callback(err);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function runBuild(builder, readFile, callback) {
|
|
|
|
if (builder.config.downloadStartDelay) {
|
|
|
|
setTimeout(function() {
|
|
|
|
buildStart(builder, readFile, callback);
|
|
|
|
}, builder.config.downloadStartDelay);
|
|
|
|
} else {
|
|
|
|
buildStart(builder, readFile, callback);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function buildStart(builder, readFile, callback) {
|
|
|
|
builder.startTime = new Date().getTime();
|
|
|
|
builder.emit('begin');
|
|
|
|
builder.emit('log','info','build begin for: '+builder.config.linkTarget);
|
|
|
|
if (builder.config.downloadForce) {
|
|
|
|
builder.emit('log','info','build using forced downloads.');
|
|
|
|
}
|
|
|
|
var targetFile = mapLocalFileSync(builder, builder.config.linkTarget);
|
|
|
|
NodeFsExtra.ensureFile(targetFile, function (err) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
builder.emit('file-write-pre',targetFile);
|
|
|
|
NodeFsExtra.writeFile(targetFile, builder.config.assetHeader, function(err) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
buildAsset(builder, targetFile, readFile, callback);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function buildAsset(builder, targetFile, readFile, callback) {
|
|
|
|
var uriList = builder.config.linkSources;
|
|
|
|
var localFileList = [];
|
|
|
|
var resultUriList = [];
|
|
|
|
var downloadList = [];
|
|
|
|
|
|
|
|
for (i = 0; i < uriList.length; i++) {
|
|
|
|
var assetItem = uriList[i];
|
|
|
|
|
|
|
|
var remoteUrl = null;
|
|
|
|
var remoteForce = null;
|
|
|
|
var remoteIndex = assetItem.indexOf('@');
|
|
|
|
if (remoteIndex > 0) {
|
|
|
|
remoteUrl = assetItem.substring(remoteIndex + 1);
|
|
|
|
if (remoteUrl.indexOf('@') == 0) {
|
|
|
|
remoteUrl = assetItem.substring(remoteIndex + 2);
|
|
|
|
remoteForce = true;
|
|
|
|
}
|
|
|
|
assetItem = assetItem.substring(0,remoteIndex);
|
|
|
|
}
|
|
|
|
var localFile = mapLocalFileSync(builder, assetItem);
|
|
|
|
|
|
|
|
// override force on all files
|
|
|
|
if (remoteForce==null && builder.config.downloadForce != null) {
|
|
|
|
remoteForce = builder.config.downloadForce;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (remoteUrl && (remoteForce || !NodeFs.existsSync(localFile))) {
|
|
|
|
downloadList.push({
|
|
|
|
remoteUrl: remoteUrl,
|
|
|
|
localFile: localFile
|
|
|
|
});
|
|
|
|
}
|
|
|
|
localFileList.push(localFile);
|
|
|
|
resultUriList.push(assetItem);
|
|
|
|
}
|
|
|
|
downloadList.reverse();
|
|
|
|
localFileList.reverse();
|
|
|
|
|
|
|
|
downloadFileList(builder, downloadList, function(error) {
|
|
|
|
if (error) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
//for(....
|
|
|
|
//if (!NodeFs.existsSync(localFile)) {
|
|
|
|
// Log.warn("illegal entry: "+localFile);
|
|
|
|
// continue;
|
|
|
|
//}
|
|
|
|
aggregateFileList(builder, targetFile, localFileList, readFile, function(err) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
buildEnd(builder, targetFile, resultUriList, callback);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function mapLocalFileSync(builder, linkSource) {
|
|
|
|
var uriMapping = builder.config.linkMapping;
|
|
|
|
if (uriMapping) {
|
|
|
|
uriMappingKeys = Object.keys(uriMapping);
|
|
|
|
uriMappingKeys.sort(function(a, b) {
|
|
|
|
return a.length < b.length; // longest first as we break on first hit
|
|
|
|
});
|
|
|
|
for (ii = 0; ii < uriMappingKeys.length; ii++) {
|
|
|
|
var uriKey = uriMappingKeys[ii];
|
|
|
|
var localPath = uriMapping[uriKey];
|
|
|
|
var mapIndex = linkSource.indexOf(uriKey);
|
|
|
|
if (mapIndex == 0) {
|
|
|
|
return localPath+linkSource.substring(uriKey.length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return linkSource;
|
|
|
|
}
|
|
|
|
|
|
|
|
function downloadFileList(builder, downloadList, callback) {
|
|
|
|
if (downloadList.length == 0) {
|
|
|
|
callback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var download = downloadList.pop();
|
|
|
|
downloadFile(builder, download.remoteUrl, download.localFile, function(err) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
downloadFileList(builder, downloadList, callback);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function downloadFile(builder, remoteUrl, localFile, callback) {
|
|
|
|
builder.emit('file-download-pre',remoteUrl);
|
|
|
|
builder.emit('log','debug','downloadFile: '+remoteUrl);
|
|
|
|
NodeFsExtra.ensureFile(localFile, function(err) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
var stream = new NodeFetch.FetchStream(remoteUrl);
|
|
|
|
stream.on('error', function(err) {
|
|
|
|
callback(err);
|
|
|
|
});
|
|
|
|
stream.on('end', function() {
|
|
|
|
builder.emit('file-download-post',remoteUrl);
|
|
|
|
callback();
|
|
|
|
});
|
|
|
|
stream.pipe(NodeFs.createWriteStream(localFile));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function aggregateFileList(builder, targetFile, aggregateList, readFile, callback) {
|
|
|
|
if (aggregateList.length == 0) {
|
|
|
|
callback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var aggregateFile = aggregateList.pop();
|
|
|
|
builder.emit('file-read-pre',aggregateFile);
|
|
|
|
readFile(aggregateFile, function(err, data) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
builder.emit('file-read-post',aggregateFile);
|
|
|
|
builder.emit('log','debug','readFile: '+aggregateFile+' size: '+data.length);
|
|
|
|
if (aggregateList.length > 0) {
|
|
|
|
data = data + builder.config.assetSeperator;
|
|
|
|
}
|
|
|
|
NodeFs.appendFile(targetFile, data, function (err) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
aggregateFileList(builder, targetFile, aggregateList, readFile, callback);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function buildEnd(builder, targetFile, resultUriList, callback) {
|
|
|
|
NodeFs.appendFile(targetFile, builder.config.assetFooter, function (err) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
builder.emit('file-write-post',targetFile);
|
|
|
|
var buildTime = new Date().getTime() - builder.startTime;
|
|
|
|
var buildResultSize = +resultUriList.length;
|
|
|
|
|
|
|
|
if (builder.config.linkTargetSingleResult) {
|
|
|
|
resultUriList = [builder.config.linkTarget];
|
|
|
|
|
|
|
|
}
|
|
|
|
var targetStats = NodeFs.statSync(targetFile);
|
|
|
|
var targetSize = targetStats['size'];
|
|
|
|
|
|
|
|
builder.emit('log','debug','target size: '+targetSize);
|
|
|
|
builder.emit('log','info','build result size: '+resultUriList.length+' from: '+buildResultSize);
|
|
|
|
builder.emit('log','info','build done in: '+buildTime+' ms.');
|
|
|
|
builder.emit('result',resultUriList);
|
|
|
|
builder.emit('end');
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function runBuilderList(builderList,callback) {
|
|
|
|
if (callback == null) {
|
|
|
|
callback = function(err) {
|
|
|
|
if(err) {
|
|
|
|
throw err; // mm
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if (builderList.length == 0) {
|
|
|
|
callback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var builder = builderList.pop();
|
|
|
|
builder.run(function(err) {
|
|
|
|
if (err) {
|
|
|
|
callback(err);
|
|
|
|
} else {
|
|
|
|
runBuilderList(builderList,callback);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function runAll(builderList,callback) {
|
|
|
|
builderList.reverse();
|
|
|
|
runBuilderList(builderList,callback);
|
|
|
|
}
|
|
|
|
|
|
|
|
function pushLinkSourcesSync(builder, linkPrefix, fileFolder) {
|
2015-02-24 14:42:32 +00:00
|
|
|
NodeFs.readdirSync(fileFolder).forEach(function (file) {
|
2015-02-24 11:35:32 +00:00
|
|
|
if (~file.indexOf('.js') || ~file.indexOf('.css')) {
|
|
|
|
builder.linkSources.push(linkPrefix+file)
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
2015-02-24 14:42:32 +00:00
|
|
|
ResourceBuilder: ResourceBuilder,
|
|
|
|
FunctionFactory: FunctionFactory,
|
|
|
|
runAll: runAll,
|
|
|
|
pushLinkSourcesSync: pushLinkSourcesSync,
|
2015-02-24 11:35:32 +00:00
|
|
|
}
|