UNPKG

jade

Version:

Jade template engine

198 lines (178 loc) 5.07 kB
/*! * 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); } }); }); }