nunta/node_modules/grunt-contrib-less/tasks/less.js
2014-08-12 22:35:56 -04:00

199 lines
5.9 KiB
JavaScript

/*
* grunt-contrib-less
* http://gruntjs.com/
*
* Copyright (c) 2014 Tyler Kellen, contributors
* Licensed under the MIT license.
*/
'use strict';
var path = require('path');
var _ = require('lodash');
var async = require('async');
var chalk = require('chalk');
var maxmin = require('maxmin');
var less = require('less');
module.exports = function(grunt) {
var lessOptions = {
parse: ['paths', 'optimization', 'filename', 'strictImports', 'syncImport', 'dumpLineNumbers', 'relativeUrls',
'rootpath'],
render: ['compress', 'cleancss', 'ieCompat', 'strictMath', 'strictUnits', 'urlArgs',
'sourceMap', 'sourceMapFilename', 'sourceMapURL', 'sourceMapBasepath', 'sourceMapRootpath', 'outputSourceFiles']
};
grunt.registerMultiTask('less', 'Compile LESS files to CSS', function() {
var done = this.async();
var options = this.options({
report: 'min',
banner: ''
});
if (this.files.length < 1) {
grunt.verbose.warn('Destination not written because no source files were provided.');
}
async.eachSeries(this.files, function(f, nextFileObj) {
var destFile = f.dest;
var files = f.src.filter(function(filepath) {
// Warn on and remove invalid source files (if nonull was set).
if (!grunt.file.exists(filepath)) {
grunt.log.warn('Source file "' + filepath + '" not found.');
return false;
} else {
return true;
}
});
if (files.length === 0) {
if (f.src.length < 1) {
grunt.log.warn('Destination not written because no source files were found.');
}
// No src files, goto next target. Warn would have been issued above.
return nextFileObj();
}
var compiledMax = [], compiledMin = [];
async.concatSeries(files, function(file, next) {
compileLess(file, options, function(css, err) {
if (!err) {
if (css.max) {
compiledMax.push(css.max);
}
compiledMin.push(css.min);
process.nextTick(next);
} else {
nextFileObj(err);
}
}, function (sourceMapContent) {
grunt.file.write(options.sourceMapFilename, sourceMapContent);
grunt.log.writeln('File ' + chalk.cyan(options.sourceMapFilename) + ' created.');
});
}, function() {
if (compiledMin.length < 1) {
grunt.log.warn('Destination not written because compiled files were empty.');
} else {
var max = compiledMax.join(grunt.util.normalizelf(grunt.util.linefeed));
var min = compiledMin.join(options.cleancss ? '' : grunt.util.normalizelf(grunt.util.linefeed));
grunt.file.write(destFile, min);
grunt.log.writeln('File ' + chalk.cyan(destFile) + ' created: ' + maxmin(max, min, options.report === 'gzip'));
}
nextFileObj();
});
}, done);
});
var compileLess = function(srcFile, options, callback, sourceMapCallback) {
options = _.assign({filename: srcFile}, options);
options.paths = options.paths || [path.dirname(srcFile)];
if (typeof options.paths === 'function') {
try {
options.paths = options.paths(srcFile);
} catch (e) {
grunt.fail.warn(wrapError(e, 'Generating @import paths failed.'));
}
}
if (typeof options.sourceMapBasepath === 'function') {
try {
options.sourceMapBasepath = options.sourceMapBasepath(srcFile);
} catch (e) {
grunt.fail.warn(wrapError(e, 'Generating sourceMapBasepath failed.'));
}
}
var css;
var srcCode = grunt.file.read(srcFile);
var parser = new less.Parser(_.pick(options, lessOptions.parse));
var additionalData = {
banner: options.banner
};
// Equivalent to --modify-vars option.
// Properties under options.modifyVars are appended as less variables
// to override global variables.
var modifyVarsOutput = parseVariableOptions(options['modifyVars']);
if (modifyVarsOutput) {
srcCode += '\n';
srcCode += modifyVarsOutput;
}
parser.parse(srcCode, function(parse_err, tree) {
if (parse_err) {
lessError(parse_err, srcFile);
callback('',true);
}
// Load custom functions
if (options.customFunctions) {
Object.keys(options.customFunctions).forEach(function(name) {
less.tree.functions[name.toLowerCase()] = function() {
var args = [].slice.call(arguments);
args.unshift(less);
var res = options.customFunctions[name].apply(this, args);
return typeof res === "object" ? res : new less.tree.Anonymous(res);
};
});
}
var minifyOptions = _.pick(options, lessOptions.render);
if (minifyOptions.sourceMapFilename) {
minifyOptions.writeSourceMap = sourceMapCallback;
}
try {
css = minify(tree, minifyOptions);
callback(css, null);
} catch (e) {
lessError(e, srcFile);
callback(css, true);
}
}, additionalData);
};
var parseVariableOptions = function(options) {
var pairs = _.pairs(options);
var output = '';
pairs.forEach(function(pair) {
output += '@' + pair[0] + ':' + pair[1] + ';';
});
return output;
};
var formatLessError = function(e) {
var pos = '[' + 'L' + e.line + ':' + ('C' + e.column) + ']';
return e.filename + ': ' + pos + ' ' + e.message;
};
var lessError = function(e, file) {
var message = less.formatError ? less.formatError(e) : formatLessError(e);
grunt.log.error(message);
grunt.fail.warn('Error compiling ' + file);
};
var wrapError = function (e, message) {
var err = new Error(message);
err.origError = e;
return err;
};
var minify = function (tree, options) {
var result = {
min: tree.toCSS(options)
};
if (!_.isEmpty(options)) {
result.max = tree.toCSS();
}
return result;
};
};