You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
198 lines
5.9 KiB
198 lines
5.9 KiB
/* |
|
* 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; |
|
}; |
|
};
|
|
|