redone with objects
This commit is contained in:
parent
8bb333ae8d
commit
3f580bbe2d
12 changed files with 725 additions and 400 deletions
63
lib/asset-assembler-config.js
Normal file
63
lib/asset-assembler-config.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
'use strict';
|
||||
|
||||
var name = 'node-ff-assets';
|
||||
var downloadTimeout = (1000*10) * 6; // 1min
|
||||
var downloadMaxSize = (1024*1024) * 10; // 10mb
|
||||
|
||||
var template = {
|
||||
downloadStartDelay: null,
|
||||
downloadForce: false,
|
||||
downloadOptions: {
|
||||
timeout: downloadTimeout,
|
||||
maxResponseLength: downloadMaxSize,
|
||||
agent: name+' remote fetcher',
|
||||
},
|
||||
|
||||
linkTargetSingleResult: true,
|
||||
linkTarget: null,
|
||||
linkSources: [],
|
||||
linkMapping: [],
|
||||
|
||||
assetType: null,
|
||||
assetHeader: '\n/* '+name+': begin */\n\n',
|
||||
assetFooter: '\n/* '+name+': end */\n\n',
|
||||
assetSeperator: '\n/* '+name+': next */\n',
|
||||
};
|
||||
|
||||
function checkConfig(config,callback) {
|
||||
if (!config) {
|
||||
callback(new Error('no config'));
|
||||
}
|
||||
if (!config.linkTarget) {
|
||||
callback(new Error('no config.linkTarget'));
|
||||
}
|
||||
if (!config.linkSources) {
|
||||
callback(new Error('no config.linkSources'));
|
||||
}
|
||||
if (!config.linkMapping) {
|
||||
callback(new Error('no config.linkMapping'));
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
function fillDefaults(config,callback) {
|
||||
if (config.downloadOptions === undefined) {
|
||||
config.downloadOptions = template.downloadOptions;
|
||||
}
|
||||
if (config.assetHeader === undefined) {
|
||||
config.assetHeader = template.assetHeader;
|
||||
}
|
||||
if (config.assetFooter === undefined) {
|
||||
config.assetFooter = template.assetFooter;
|
||||
}
|
||||
if (config.assetSeperator === undefined) {
|
||||
config.assetSeperator = template.assetSeperator;
|
||||
}
|
||||
callback(null,config);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
template: template,
|
||||
checkConfig: checkConfig,
|
||||
fillDefaults: fillDefaults,
|
||||
};
|
||||
252
lib/asset-assembler.js
Normal file
252
lib/asset-assembler.js
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
'use strict';
|
||||
|
||||
var events = require('events');
|
||||
var fetch = require('fetch');
|
||||
var fs = require('fs-extra');
|
||||
var factory = require('./factory');
|
||||
var config = require('./asset-assembler-config');
|
||||
|
||||
|
||||
function buildEnd(builder, targetFile, resultUriList, callback) {
|
||||
fs.appendFile(targetFile, builder.config.assetFooter, function (err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
var buildTime = new Date().getTime() - builder.startTime,
|
||||
buildResultSize = resultUriList.length,
|
||||
targetStats = fs.statSync(targetFile),
|
||||
targetSize = targetStats.size;
|
||||
|
||||
if (builder.config.linkTargetSingleResult) {
|
||||
resultUriList = [builder.config.linkTarget];
|
||||
}
|
||||
builder.emit('file-write-post',targetFile);
|
||||
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 downloadFile(builder, remoteUrl, localFile, callback) {
|
||||
builder.emit('file-download-pre',remoteUrl);
|
||||
builder.emit('log','debug','downloadFile: '+remoteUrl);
|
||||
fs.ensureFile(localFile, function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
var stream = new fetch.FetchStream(remoteUrl);
|
||||
stream.on('error', function(err) {
|
||||
callback(err);
|
||||
});
|
||||
stream.on('end', function() {
|
||||
builder.emit('file-download-post',remoteUrl);
|
||||
callback();
|
||||
});
|
||||
stream.pipe(fs.createWriteStream(localFile));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
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 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;
|
||||
}
|
||||
fs.appendFile(targetFile, data, function (err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
aggregateFileList(builder, targetFile, aggregateList, readFile, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function mapLocalFileSync(assembler, linkSource) {
|
||||
var uriMapping = assembler.config.linkMapping;
|
||||
if (uriMapping) {
|
||||
var uriMappingKeys = Object.keys(uriMapping);
|
||||
uriMappingKeys.sort(function(a, b) {
|
||||
return a.length < b.length; // longest first as we break on first hit
|
||||
});
|
||||
for (var 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 buildAsset(builder, targetFile, readFile, callback) {
|
||||
var uriList = builder.config.linkSources;
|
||||
var localFileList = [];
|
||||
var resultUriList = [];
|
||||
var downloadList = [];
|
||||
|
||||
for (var 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===undefined && builder.config.downloadForce !== undefined) {
|
||||
remoteForce = builder.config.downloadForce;
|
||||
}
|
||||
|
||||
if (remoteUrl && (remoteForce || !fs.existsSync(localFile))) {
|
||||
downloadList.push({
|
||||
remoteUrl: remoteUrl,
|
||||
localFile: localFile
|
||||
});
|
||||
}
|
||||
localFileList.push(localFile);
|
||||
resultUriList.push(assetItem);
|
||||
}
|
||||
downloadList.reverse();
|
||||
localFileList.reverse();
|
||||
|
||||
downloadFileList(builder, downloadList, function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
//for(....
|
||||
//if (!fs.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 buildStart(assembler, readFile, callback) {
|
||||
assembler.startTime = new Date().getTime();
|
||||
assembler.emit('begin');
|
||||
assembler.emit('log','info','build begin for: '+assembler.config.linkTarget);
|
||||
if (assembler.config.downloadForce) {
|
||||
assembler.emit('log','info','build using forced downloads.');
|
||||
}
|
||||
var targetFile = mapLocalFileSync(assembler, assembler.config.linkTarget);
|
||||
fs.ensureFile(targetFile, function (err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
assembler.emit('file-write-pre',targetFile);
|
||||
fs.writeFile(targetFile, assembler.config.assetHeader, function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
buildAsset(assembler, targetFile, readFile, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function runAssembler(assembler, readFile, callback) {
|
||||
if (assembler.config.downloadStartDelay) {
|
||||
setTimeout(function() {
|
||||
buildStart(assembler, readFile, callback);
|
||||
}, assembler.config.downloadStartDelay);
|
||||
} else {
|
||||
buildStart(assembler, readFile, callback);
|
||||
}
|
||||
}
|
||||
|
||||
function runAssemblerSafe(assembler, readFile, callback) {
|
||||
runAssembler(assembler, readFile, function(err) {
|
||||
// send error
|
||||
if (err) {
|
||||
assembler.emit('error',err);
|
||||
}
|
||||
// finish callback
|
||||
if (callback !== undefined) {
|
||||
callback(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function AssetAssembler(config, readFile) {
|
||||
this.config = config;
|
||||
this.startTime = 0;
|
||||
this.readFile = readFile || factory.assembler.constructor.readFile();
|
||||
events.EventEmitter.call(this);
|
||||
}
|
||||
|
||||
AssetAssembler.prototype.__proto__ = events.EventEmitter.prototype;
|
||||
|
||||
AssetAssembler.prototype.run = function(callback) {
|
||||
if (callback === undefined) {
|
||||
throw new Error('no callback');
|
||||
}
|
||||
var self = this;
|
||||
config.checkConfig(self.config,function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
config.fillDefaults(self.config, function (err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
runAssemblerSafe(self, self.readFile, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = AssetAssembler;
|
||||
57
lib/assets-builder-config.js
Normal file
57
lib/assets-builder-config.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
'use strict';
|
||||
|
||||
var factory = require('./factory');
|
||||
|
||||
var template = {
|
||||
buildOrder: ['css','js'],
|
||||
|
||||
configCreate: null,
|
||||
configFill: factory.builder.configCreate.nop(),
|
||||
assemblerCreate: factory.builder.assemblerCreate.readFile(),
|
||||
assemblerFill: factory.builder.configCreate.nop(),
|
||||
|
||||
assets: {
|
||||
js: {
|
||||
configCreate: null,
|
||||
configFill: null,
|
||||
assemblerCreate: null,
|
||||
assemblerFill: null,
|
||||
},
|
||||
css: {
|
||||
configCreate: null,
|
||||
configFill: null,
|
||||
assemblerCreate: null,
|
||||
assemblerFill: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function checkConfig(config,callback) {
|
||||
if (!config) {
|
||||
callback(new Error('no config'));
|
||||
}
|
||||
// TODO: add more
|
||||
callback();
|
||||
}
|
||||
|
||||
function fillDefaults(config,callback) {
|
||||
if (config.buildOrder === undefined) {
|
||||
config.buildOrder = Object.keys(config.assets);
|
||||
}
|
||||
if (config.configFill === undefined) {
|
||||
config.configFill = template.configFill;
|
||||
}
|
||||
if (config.assemblerCreate === undefined) {
|
||||
config.assemblerCreate = template.assemblerCreate;
|
||||
}
|
||||
if (config.assemblerFill === undefined) {
|
||||
config.assemblerFill = template.assemblerFill;
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
template: template,
|
||||
checkConfig: checkConfig,
|
||||
fillDefaults: fillDefaults,
|
||||
};
|
||||
115
lib/assets-builder.js
Normal file
115
lib/assets-builder.js
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
'use strict';
|
||||
|
||||
var events = require('events');
|
||||
var async = require('async');
|
||||
var config = require('./assets-builder-config');
|
||||
|
||||
function buildStepAssemblerRun(builder, buildConfig, assembler, callback) {
|
||||
assembler.run(callback);
|
||||
}
|
||||
|
||||
function buildStepAssemblerFill(builder, buildConfig, assembler, callback) {
|
||||
var assemblerFill = buildConfig.assemblerFill || builder.config.assemblerFill;
|
||||
assemblerFill(assembler, function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
buildStepAssemblerRun(builder, buildConfig, assembler, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildStepAssemblerCreate(builder, buildConfig, assemblerConfig, callback) {
|
||||
var assemblerCreate = buildConfig.assemblerCreate || builder.config.assemblerCreate;
|
||||
assemblerCreate(assemblerConfig, function(err, assembler) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
buildStepAssemblerFill(builder, buildConfig, assembler, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildStepConfigFill(builder, buildConfig, assemblerConfig, callback) {
|
||||
var configFill = buildConfig.configFill || builder.config.configFill;
|
||||
|
||||
if (assemblerConfig.assetType === undefined) {
|
||||
assemblerConfig.assetType = builder.currentAssetType;
|
||||
}
|
||||
|
||||
configFill(assemblerConfig, function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
buildStepAssemblerCreate(builder, buildConfig, assemblerConfig, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildStepConfigCreate(builder, buildConfig, callback) {
|
||||
var configCreate = buildConfig.configCreate || builder.config.configCreate;
|
||||
configCreate(function(err, assemblerConfig) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
buildStepConfigFill(builder, buildConfig, assemblerConfig, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function AssetsBuilder(config) {
|
||||
this.config = config;
|
||||
this.startTime = 0;
|
||||
this.currentAssetType = '';
|
||||
events.EventEmitter.call(this);
|
||||
}
|
||||
|
||||
AssetsBuilder.prototype.__proto__ = events.EventEmitter.prototype;
|
||||
|
||||
AssetsBuilder.prototype.runAll = function(callback) {
|
||||
var self = this;
|
||||
config.checkConfig(self.config,function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
config.fillDefaults(self.config, function (err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
var runners = [];
|
||||
for (var i = 0; i < self.config.buildOrder.length; i++) {
|
||||
var assetKey = self.config.buildOrder[i];
|
||||
runners.push(self.runAsset(assetKey));
|
||||
}
|
||||
async.series(runners,self.config.buildDone);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
AssetsBuilder.prototype.runAsset = function(name) {
|
||||
var self = this;
|
||||
return function(callback) {
|
||||
if (name === undefined) {
|
||||
throw new Error('no name');
|
||||
}
|
||||
if (callback === undefined) {
|
||||
throw new Error('no callback');
|
||||
}
|
||||
config.checkConfig(self.config,function(err) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
var buildConfig = self.config.assets[name];
|
||||
if (buildConfig === undefined) {
|
||||
throw new Error('no asset config found');
|
||||
}
|
||||
self.currentAssetType = name;
|
||||
buildStepConfigCreate(self,buildConfig,callback);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = AssetsBuilder;
|
||||
144
lib/factory.js
Normal file
144
lib/factory.js
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
|
||||
var assemblerConstructors = {
|
||||
readFile : function() {
|
||||
return function(file, callback) {
|
||||
fs.readFile(file, function(err, data) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, data);
|
||||
}
|
||||
});
|
||||
};
|
||||
},
|
||||
readFileMinify : function(options) {
|
||||
if (options === undefined) {
|
||||
options = {};
|
||||
}
|
||||
var minify = require('minify');
|
||||
return function(file, callback) {
|
||||
minify(file, options, function(err, data) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
callback(null, data);
|
||||
}
|
||||
});
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
var assemblerEventLog = {
|
||||
console : function(logType) {
|
||||
if (logType === undefined) {
|
||||
throw new Error('no logType');
|
||||
}
|
||||
return function(logLevel, logMessage) {
|
||||
console.log(logLevel + ': node-ff-assets-' + logType + ' ' + logMessage);
|
||||
};
|
||||
},
|
||||
winston: function(logger, logType) {
|
||||
if (logger === undefined) {
|
||||
throw new Error('no logger');
|
||||
}
|
||||
if (logType === undefined) {
|
||||
throw new Error('no logType');
|
||||
}
|
||||
return function(logLevel, logMessage) {
|
||||
logger.log(logLevel, 'node-ff-assets-'+logType+' '+logMessage);
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
var assemblerEventResult = {
|
||||
objectFunction : function(object, functionName, resultKey) {
|
||||
if (object === undefined) {
|
||||
throw new Error('no object');
|
||||
}
|
||||
if (functionName === undefined) {
|
||||
throw new Error('no functionName');
|
||||
}
|
||||
return function(resultValue) {
|
||||
if (resultKey === undefined) {
|
||||
object[functionName](resultValue);
|
||||
} else {
|
||||
object[functionName](resultKey, resultValue);
|
||||
}
|
||||
};
|
||||
},
|
||||
objectAdd : function(object, resultKey) {
|
||||
return assemblerEventResult.objectFunction(object, 'add', resultKey);
|
||||
},
|
||||
objectSet : function(object, resultKey) {
|
||||
return assemblerEventResult.objectFunction(object, 'set', resultKey);
|
||||
},
|
||||
objectPut : function(object, resultKey) {
|
||||
return assemblerEventResult.objectFunction(object, 'put', resultKey);
|
||||
},
|
||||
arrayPush : function(object) {
|
||||
return assemblerEventResult.objectFunction(object, 'push');
|
||||
},
|
||||
};
|
||||
|
||||
var libAsync = {
|
||||
pushLinkSources: function (builder, linkPrefix, fileFolder) {
|
||||
return function(callback) {
|
||||
fs.readdir(fileFolder, function (err, fileList) {
|
||||
if (err) {
|
||||
callback(err);
|
||||
} else {
|
||||
fileList.forEach(function (file) {
|
||||
if (file.indexOf('.js') > 0 || file.indexOf('.css') > 0) {
|
||||
builder.linkSources.push(linkPrefix+file);
|
||||
}
|
||||
});
|
||||
callback(null,null);
|
||||
}
|
||||
});
|
||||
};
|
||||
},
|
||||
runBuilder: function (builder) {
|
||||
return function(callback) {
|
||||
builder.run(callback);
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
var builderAssemblerCreate = {
|
||||
readFile: function () {
|
||||
return function (assemblerConfig, callback) {
|
||||
var AS = require('./asset-assembler');
|
||||
callback(null,new AS(assemblerConfig,assemblerConstructors.readFile()));
|
||||
};
|
||||
},
|
||||
readFileMinify: function(options) {
|
||||
return function (assemblerConfig, callback) {
|
||||
var AS = require('./asset-assembler');
|
||||
callback(null,new AS(assemblerConfig,assemblerConstructors.readFileMinify(options)));
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
var builderEmptyCallback = function () {};
|
||||
|
||||
module.exports = {
|
||||
assembler: {
|
||||
constructor : assemblerConstructors,
|
||||
event : {
|
||||
log : assemblerEventLog,
|
||||
result : assemblerEventResult,
|
||||
},
|
||||
},
|
||||
builder: {
|
||||
configCreate: { nop: builderEmptyCallback },
|
||||
configFill: { nop: builderEmptyCallback },
|
||||
assemblerCreate: builderAssemblerCreate,
|
||||
assemblerFill: { nop: builderEmptyCallback },
|
||||
},
|
||||
lib: {
|
||||
async: libAsync,
|
||||
},
|
||||
};
|
||||
16
lib/node-ff-assets.js
Normal file
16
lib/node-ff-assets.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
var assets = {
|
||||
factory: require('./factory'),
|
||||
AssetAssembler: require('./asset-assembler'),
|
||||
checkAssemblerConfig: require('./asset-assembler-config').checkConfig,
|
||||
AssetsBuilder: require('./assets-builder'),
|
||||
checkBuilderConfig: require('./assets-builder-config').checkConfig,
|
||||
};
|
||||
|
||||
assets.build = function (config,callback) {
|
||||
var builder = new assets.AssetsBuilder(config);
|
||||
builder.runAll(callback);
|
||||
};
|
||||
|
||||
module.exports = assets;
|
||||
Loading…
Add table
Add a link
Reference in a new issue