diff options
Diffstat (limited to 'node_modules/stylus/lib/middleware.js')
| -rw-r--r-- | node_modules/stylus/lib/middleware.js | 256 |
1 files changed, 256 insertions, 0 deletions
diff --git a/node_modules/stylus/lib/middleware.js b/node_modules/stylus/lib/middleware.js new file mode 100644 index 00000000..b6e901a3 --- /dev/null +++ b/node_modules/stylus/lib/middleware.js @@ -0,0 +1,256 @@ +/*! + * Stylus - middleware + * Copyright (c) Automattic <developer.wordpress.com> + * MIT Licensed + */ + +/** + * Module dependencies. + */ + +var stylus = require('./stylus') + , fs = require('fs') + , url = require('url') + , dirname = require('path').dirname + , mkdirp = require('mkdirp') + , join = require('path').join + , sep = require('path').sep + , debug = require('debug')('stylus:middleware'); + +/** + * Import map. + */ + +var imports = {}; + +/** + * Return Connect middleware with the given `options`. + * + * Options: + * + * `force` Always re-compile + * `src` Source directory used to find .styl files, + * a string or function accepting `(path)` of request. + * `dest` Destination directory used to output .css files, + * a string or function accepting `(path)` of request, + * when undefined defaults to `src`. + * `compile` Custom compile function, accepting the arguments + * `(str, path)`. + * `compress` Whether the output .css files should be compressed + * `firebug` Emits debug infos in the generated CSS that can + * be used by the FireStylus Firebug plugin + * `linenos` Emits comments in the generated CSS indicating + * the corresponding Stylus line + * 'sourcemap' Generates a sourcemap in sourcemaps v3 format + * + * 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 `static()` layer setup to serve the .css + * files generated by Stylus. + * + * var app = connect(); + * + * app.middleware({ + * src: __dirname + * , dest: __dirname + '/public' + * , compile: compile + * }) + * + * app.use(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 || src; + + // Default compile callback + options.compile = options.compile || function(str, path){ + // inline sourcemap + if (options.sourcemap) { + if ('boolean' == typeof options.sourcemap) + options.sourcemap = {}; + options.sourcemap.inline = true; + } + + return stylus(str) + .set('filename', path) + .set('compress', options.compress) + .set('firebug', options.firebug) + .set('linenos', options.linenos) + .set('sourcemap', options.sourcemap); + }; + + // 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)) { + + if (typeof dest == 'string') { + // check for dest-path overlap + var overlap = compare(dest, path).length; + if ('/' == path.charAt(0)) overlap++; + path = path.slice(overlap); + } + + var cssPath, stylusPath; + cssPath = (typeof dest == 'function') + ? dest(path) + : join(dest, path); + stylusPath = (typeof src == 'function') + ? src(path) + : join(src, path.replace('.css', '.styl')); + + // 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() { + debug('read %s', cssPath); + fs.readFile(stylusPath, 'utf8', function(err, str){ + if (err) return error(err); + var style = options.compile(str, stylusPath); + var paths = style.options._imports = []; + imports[stylusPath] = null; + style.render(function(err, css){ + if (err) return next(err); + debug('render %s', stylusPath); + imports[stylusPath] = paths; + mkdirp(dirname(cssPath), parseInt('0700', 8), 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) { + debug('not found %s', cssPath); + compile(); + } else { + next(err); + } + } else { + // Source has changed, compile it + if (stylusStats.mtime > cssStats.mtime) { + debug('modified %s', cssPath); + compile(); + // Already compiled, check imports + } else { + checkImports(stylusPath, function(changed){ + if (debug && changed.length) { + changed.forEach(function(path) { + debug('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); + }); + }); +} + +/** + * get the overlaping path from the end of path A, and the begining of path B. + * + * @param {String} pathA + * @param {String} pathB + * @return {String} + * @api private + */ + +function compare(pathA, pathB) { + pathA = pathA.split(sep); + pathB = pathB.split('/'); + if (!pathA[pathA.length - 1]) pathA.pop(); + if (!pathB[0]) pathB.shift(); + var overlap = []; + + while (pathA[pathA.length - 1] == pathB[0]) { + overlap.push(pathA.pop()); + pathB.shift(); + } + return overlap.join('/'); +} |
