jade
Version:
Jade template engine
198 lines (178 loc) • 5.07 kB
JavaScript
/*!
* Stylus - middleware
* Copyright(c) 2010 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var stylus = require('./stylus')
, fs = require('fs')
, url = require('url')
, basename = require('path').basename
, join = require('path').join
, ENOENT;
// COMPAT:
try {
ENOENT = require('constants').ENOENT;
} catch (err) {
ENOENT = process.ENOENT;
}
/**
* Import map.
*/
var imports = {};
/**
* Return Connect middleware with the given `options`.
*
* Options:
*
* `force` Always re-compile
* `src` Source directory used to find .styl files
* `dest` Destination directory used to output .css files
* when undefined defaults to `src`.
* `compile` Custom compile function, accepting the arguments
* `(str, path)`.
* `compress` Whether the output .css files should be compressed
*
* Examples:
*
* Here we set up the custom compile function so that we may
* set the `compress` option, or define additional functions.
*
* By default the compile function simply sets the `filename`
* and renders the CSS.
*
* function compile(str, path) {
* return stylus(str)
* .set('filename', path)
* .set('compress', true);
* }
*
* Pass the middleware to Connect, grabbing .styl 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 Stylus.
*
* var server = connect.createServer(
* stylus.middleware({
* src: __dirname
* , dest: __dirname + '/public'
* , compile: compile
* })
* , connect.static(__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;
// Source dir required
var src = options.src;
if (!src) throw new Error('stylus.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){
return stylus(str)
.set('filename', path)
.set('compress', options.compress);
};
// Middleware
return function(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', '.styl'));
// Ignore ENOENT to fall through as 404
function error(err) {
next(ENOENT == err.errno
? null
: err);
}
// Force
if (force) return compile();
// Compile to cssPath
function compile() {
fs.readFile(stylusPath, 'utf8', function(err, str){
if (err) return error(err);
var style = options.compile(str, stylusPath);
var paths = style.options._imports = [];
style.render(function(err, css){
imports[stylusPath] = imports[stylusPath] || paths;
if (err) return next(err);
fs.writeFile(cssPath, css, 'utf8', function(err){
next(err);
});
});
});
}
// 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.errno) {
compile();
} else {
next(err);
}
} else {
// Source has changed, compile it
if (stylusStats.mtime > cssStats.mtime) {
compile();
// Already compiled, check imports
} else {
checkImports(stylusPath, function(changed){
changed ? 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 = false;
nodes.forEach(function(import){
fs.stat(import.path, function(err, stat){
if (err) {
--pending || fn(changed);
} else if (import.mtime) {
changed = changed || stat.mtime > import.mtime;
import.mtime = stat.mtime;
--pending || fn(changed);
} else {
import.mtime = stat.mtime;
--pending || fn(changed = true);
}
});
});
}