mincer
Version:
Web assets processor. Native JavaScript port of Sprockets.
204 lines (156 loc) • 4.79 kB
JavaScript
/**
* class LessEngine
*
* Engine for the Less compiler. You will need `less` Node module installed
* in order to use [[Mincer]] with `*.less` files:
*
* npm install less
*
*
* ##### SUBCLASS OF
*
* [[Template]]
**/
;
// stdlib
var path = require('path');
// 3rd-party
var _ = require('lodash');
var less; // initialized later
// internal
var Template = require('../template');
var prop = require('../common').prop;
////////////////////////////////////////////////////////////////////////////////
// Class constructor
var LessEngine = module.exports = function LessEngine() {
Template.apply(this, arguments);
less = less || Template.libs.less || require('less');
};
require('util').inherits(LessEngine, Template);
// helper to generate human-friendly errors.
// adapted version from original bin/less
function lessError(ctx /*, options*/) {
var message = '';
var extract = ctx.extract;
var error = [];
if (ctx.stack || !ctx.hasOwnProperty('index')) { return ctx; }
if (typeof (extract[0]) === 'string') {
error.push((ctx.line - 1) + ' ' + extract[0]);
}
if (extract[1]) {
error.push(ctx.line + ' ' +
extract[1].slice(0, ctx.column) +
extract[1][ctx.column] +
extract[1].slice(ctx.column + 1));
}
if (typeof (extract[2]) === 'string') {
error.push((ctx.line + 1) + ' ' + extract[2]);
}
error = error.join('\n');
message = ctx.type + 'Error: ' + ctx.message;
if (ctx.filename) {
message += ' in ' + ctx.filename + ':' + ctx.line + ':' + ctx.column;
}
return new Error(message + '\n---\n' + error);
}
// Render data
LessEngine.prototype.evaluate = function (context, locals) {
var error = null,
self = this,
withSourcemap = context.environment.isEnabled('source_maps');
_.forEach(locals, function (localFunc, localName) {
function func() {
var extractedLessArguments = _.map(arguments, 'value');
return new less.tree.Anonymous(localFunc.apply(null, extractedLessArguments));
}
if (less.version[0] < 2) {
less.tree.functions[localName] = func;
} else {
less.functions.functionRegistry.add(localName, func);
}
});
//
// Legacy (< 2.x) API support
//
if (less.version[0] < 2) {
var parser = new (less.Parser)({
paths: [ path.dirname(this.file) ].concat(context.environment.paths),
optimization: 1,
filename: this.file,
syncImport: true,
strictImports: false
});
parser.parse(self.data, function (err, tree) {
if (err) {
error = lessError(err);
return;
}
_.keys(parser.imports.files).forEach(function (file) {
context.dependOn(file);
});
try {
if (!withSourcemap) {
self.data = tree.toCSS();
} else {
self.data = tree.toCSS({
sourceMap: true,
outputSourceFiles: true,
// sourceMapBasepath: context.environment.root,
// sourceMapRootpath: '/',
writeSourceMap: function (output) {
// Manually convert paths to relative, because LESS
// can make tose absolute for complex cases.
var map = JSON.parse(output);
var dir = path.dirname(context.pathname);
map.sources.forEach(function (file, idx) {
var rel = path.relative(dir, file);
if (path.sep === '\\') { rel = rel.replace('\\', '/'); }
map.sources[idx] = rel;
});
self.map = JSON.stringify(map);
}
});
}
} catch (e) {
error = lessError(e);
}
});
if (error) { throw error; }
return;
}
//
// 2.+ API
//
less.render(self.data, {
paths: [ path.dirname(this.file) ].concat(context.environment.paths),
optimization: 1,
filename: this.file,
syncImport: true,
strictImports: false,
sourceMap: !withSourcemap ? false : {
outputSourceFiles: true
}
}, function (err, output) {
if (err) {
error = lessError(err);
return;
}
output.imports.forEach(function (file) {
context.dependOn(file);
});
self.data = output.css;
if (withSourcemap) {
var map = JSON.parse(output.map);
var dir = path.dirname(context.pathname);
map.sources.forEach(function (file, idx) {
var rel = path.relative(dir, file);
if (path.sep === '\\') { rel = rel.replace('\\', '/'); }
map.sources[idx] = rel;
});
self.map = JSON.stringify(map);
}
});
if (error) { throw error; }
};
// Expose default MimeType of an engine
prop(LessEngine, 'defaultMimeType', 'text/css');