node-asset-pipeline
Version:
Asset pipeline ala rails for node
209 lines (165 loc) • 4.55 kB
JavaScript
/*jshint camelcase:false */
/**
* The bootstrap file.
*
*/
var grunt = require('grunt'),
_ = grunt.util._,
manifest = require('./asset-manifest'),
helpers = require('grunt-ss-helpers'),
log = helpers.log,
util = require('util'),
assetIter = require('./asset-itterator'),
Map = require('../node_modules/collections/map'),
pace = require('pace'),
when = require('when');
var assets = module.exports = {
Map: Map
};
// default throttle of concurent operations
assets.THROTTLE_OPS = 100;
/**
* [run description]
* @param {Array.<Object>} files
* @param {Object} options
* @param {string} target
* @return {Object} a promise.
*/
assets.run = function( files, options, target) {
var task = new assets.Task(files, options, target);
return task.run();
};
/**
* [task description]
* @param {Array.<Object>} files
* @param {Object} options
* @param {string} target
*/
assets.Task = function( files, options, target ) {
assetIter.apply(this, arguments);
// make a copy of the files array
this.files = Array.prototype.slice.call(files, 0);
this.masterDeferred = when.defer();
this.fileObj = null;
this.options = options;
this.target = target;
this._assetFiles = [];
// internal, sanitized parameters
this._params = {};
this.destFolder = null;
this.relPath = '';
this.prepend = '';
// progress for scan
this._paceScan = null;
// throttle of concurent operations
this.opThrottle = 100;
this.debug = false;
};
util.inherits(assets.Task, assetIter);
/**
* [run description]
* @return {when.Promise} a promise.
*/
assets.Task.prototype.run = function() {
var def = when.defer();
//
// read or initialize manifest
//
if ( !manifest.init( this.options.manifest ) ) {
return def.reject('No manifest file');
}
//
// Check options
//
if ( !this.validateOptions() ) {
return def.reject('options did not pass validations');
}
return when.map(this.files, this.runFileObj.bind(this) );
};
/**
* Execute the asset task for one fileobject src/dest pair.
*
* @param {Object} The grunt file object.
* @return {when.Promise} a promise.
*/
assets.Task.prototype.runFileObj = function( fileObj ) {
var def = when.defer();
//
// Validate file Object
//
if ( !this.validate( fileObj ) ) {
return def.reject('validation error');
}
//
// expand the source files
//
var assetFiles = grunt.file.expand({filter: 'isFile'}, fileObj.src);
if ( 0 === assetFiles.length ) {
helpers.log.info('No source files found. Done.');
return def.resolve('No source files');
}
//
// Go through each file and md5 it...
//
log.info('\nStart scanning ' + (assetFiles.length + '').cyan +
' files for changes based on m5 hash');
if (this._progress) { this._paceScan = pace( assetFiles.length ); }
return this.itterateFiles( assetFiles )
.then( manifest.save );
};
/**
* Validates and prepares paths
*
* @param {Object} fileObj grunt file object.
* @return {boolean} error message yielded in this func.
*/
assets.Task.prototype.validate = function( fileObj ) {
var dest = fileObj.dest;
if ( 'string' !== typeof dest ) {
helpers.log.error('"dest" must be defined, and be of type "string"');
return false;
}
// check if cwd
if ( 0 <= ['', '.', './', '..', '../'].indexOf(dest) ) {
helpers.log.error('"dest" cannot be the current working directory');
return false;
}
// check if it exists and create
if ( !grunt.file.exists(dest) ) {
grunt.file.mkdir(dest);
}
if ( !grunt.file.isDir(dest)) {
helpers.log.error('"dest" is not a directory: ' + dest);
return false;
}
// good to assign
this.destFolder = dest;
return true;
};
/**
* Perform granular validations on options
* and define the internal params properties.
*
* @return {boolean}
*/
assets.Task.prototype.validateOptions = function() {
this.debug = !!this.options.debug;
//
// cdn url
//
var cdnurl = this.options.cdnurl || '';
manifest.setConf('cdnurl', cdnurl);
this._progress = !!this.options.progress;
if ( isNaN(this.opThrottle) ) {
this.opThrottle = assets.THROTTLE_OPS;
}
if ( this.options.rel ) {
this.relPath = grunt.file.expand({ filter: 'isDirectory' }, this.options.rel )[0];
if (!_.isString(this.relPath)) {
log.error('\nRelative path does not exist: '.red + this.options.rel );
return false;
}
}
this.prepend = this.options.prepend || '';
return true;
};