Created
April 15, 2012 16:02
-
-
Save kaareal/2393577 to your computer and use it in GitHub Desktop.
Less middleware for compiling less files that working nicely with @import and updates when you change the imported files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* Lifted from https://github.com/LearnBoost/stylus/blob/master/lib/middleware.js | |
* Refactored to work with less instead of stylus and invoking compile when you change a "@import file" | |
*/ | |
/*! | |
* Stylus - middleware | |
* Copyright(c) 2010 LearnBoost <[email protected]> | |
* MIT Licensed | |
*/ | |
/** | |
* Module dependencies. | |
*/ | |
var less = require('less') | |
, fs = require('fs') | |
, url = require('url') | |
, basename = require('path').basename | |
, dirname = require('path').dirname | |
, mkdirp = require('mkdirp') | |
, join = require('path').join; | |
/** | |
* Import map. | |
*/ | |
var imports = {}; | |
/** | |
* Return Connect middleware with the given `options`. | |
* | |
* Options: | |
* | |
* `force` Always re-compile | |
* `debug` Output debugging information | |
* `src` Source directory used to find .styl files | |
* `dest` Destination directory used to output .css files | |
* when undefined defaults to `src`. | |
* `compress` Whether the output .css files should be compressed | |
* | |
* | |
* Examples: | |
* | |
* Pass the middleware to Connect, grabbing .less files from this directory | |
* and saving .css files to _./public_. Also supplying our custom `compile` function. | |
* | |
* Following that we have a `staticProvider` layer setup to serve the .css | |
* files generated by Less. | |
* | |
* app.use( | |
* lessMiddleware({ | |
* src: __dirname | |
* , dest: __dirname + '/public' | |
* }) | |
* ); | |
* | |
* | |
* @param {Object} options | |
* @return {Function} | |
* @api public | |
*/ | |
module.exports = function(options){ | |
options = options || {}; | |
// Accept src/dest dir | |
if ('string' == typeof options) { | |
options = { src: options }; | |
} | |
// Force compilation | |
var force = options.force; | |
// Debug option | |
var debug = options.debug; | |
// Source dir required | |
var src = options.src; | |
if (!src) throw new Error('less.middleware() requires "src" directory'); | |
// Default dest dir to source | |
var dest = options.dest | |
? options.dest | |
: src; | |
// Default compile callback | |
options.compile = options.compile || function(str, path){ | |
console.log('compiler'); | |
}; | |
// Middleware | |
return function stylus(req, res, next) { | |
if ('GET' != req.method && 'HEAD' != req.method) return next(); | |
var path = url.parse(req.url).pathname; | |
if (/\.css$/.test(path)) { | |
var cssPath = join(dest, path) | |
, stylusPath = join(src, path.replace('.css', '.less')); | |
if (debug) { | |
log('source', stylusPath); | |
log('dest', cssPath); | |
} | |
// Ignore ENOENT to fall through as 404 | |
function error(err) { | |
next('ENOENT' == err.code | |
? null | |
: err); | |
} | |
// Force | |
if (force) return compile(); | |
// Compile to cssPath | |
function compile() { | |
if (debug) log('read', stylusPath); | |
fs.readFile(stylusPath, 'utf8', function(err, str){ | |
if (err) return error(err); | |
var parser = new(less.Parser)({ | |
paths: [dirname(stylusPath)], // Specify search paths for @import directives | |
filename: stylusPath // Specify a filename, for better error messages | |
}); | |
delete imports[stylusPath]; | |
parser.parse(str, function (e, tree) { | |
if (err) return next(err); | |
if (debug) log('render', stylusPath); | |
//console.log( stylusPath.replace(dirname(stylusPath), ''); | |
imports[stylusPath] = tree.rules | |
.filter(function(rule) { | |
return rule.path; | |
}) | |
.map(function(rule) { | |
return { | |
mtime : Date.now(), | |
path : join(dirname(stylusPath), rule.path) | |
}; | |
}); | |
var css = tree.toCSS({ compress: options.compress }) // Minify CSS output | |
mkdirp(dirname(cssPath), 0700, function(err){ | |
if (err) return error(err); | |
fs.writeFile(cssPath, css, 'utf8', next); | |
}); | |
}); | |
}); | |
} | |
// Re-compile on server restart, disregarding | |
// mtimes since we need to map imports | |
if (!imports[stylusPath]) return compile(); | |
// Compare mtimes | |
fs.stat(stylusPath, function(err, stylusStats){ | |
if (err) return error(err); | |
fs.stat(cssPath, function(err, cssStats){ | |
// CSS has not been compiled, compile it! | |
if (err) { | |
if ('ENOENT' == err.code) { | |
if (debug) log('not found', cssPath); | |
compile(); | |
} else { | |
next(err); | |
} | |
} else { | |
// Source has changed, compile it | |
if (stylusStats.mtime > cssStats.mtime) { | |
if (debug) log('modified', cssPath); | |
compile(); | |
// Already compiled, check imports | |
} else { | |
checkImports(stylusPath, function(changed){ | |
if (debug && changed.length) { | |
changed.forEach(function(path) { | |
log('modified import %s', path); | |
}); | |
} | |
changed.length ? compile() : next(); | |
}); | |
} | |
} | |
}); | |
}); | |
} else { | |
next(); | |
} | |
} | |
}; | |
/** | |
* Check `path`'s imports to see if they have been altered. | |
* | |
* @param {String} path | |
* @param {Function} fn | |
* @api private | |
*/ | |
function checkImports(path, fn) { | |
var nodes = imports[path]; | |
if (!nodes) return fn(); | |
if (!nodes.length) return fn(); | |
var pending = nodes.length | |
, changed = []; | |
nodes.forEach(function(imported){ | |
fs.stat(imported.path, function(err, stat) { | |
// error or newer mtime | |
if (err || !imported.mtime || stat.mtime > imported.mtime) { | |
changed.push(imported.path); | |
} | |
--pending || fn(changed); | |
}); | |
}); | |
} | |
/** | |
* Log a message. | |
* | |
* @api private | |
*/ | |
function log(key, val) { | |
console.error(' \033[90m%s :\033[0m \033[36m%s\033[0m', key, val); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment