UNPKG

nodulator

Version:

Complete NodeJS Framework for Restfull APIs

176 lines (147 loc) 5.11 kB
/*! * cofee-script - middleware (adapted from the less-middleware) * * Original Copyright(c) 2010 LearnBoost <dev@learnboost.com> * MIT Licensed */ var coffee = require('coffee-script'), fs = require('fs'), url = require('url'), path = require('path'), mkdirp = require('mkdirp'), convertSourceMap = require('convert-source-map'); /** * Return Connect middleware with the given `options`. * * Options: * * `force` Always re-compile * `once` Only re-compile the one time * `debug` Output debugging information * `bare` Compile the JavaScript without the top-level function safety wrapper * `src` Source directory used to find .coffee files * `encodeSrc` Encode CoffeeScript source file as base64 comment in compiled JavaScript * `prefix` Path which should be stripped from `pathname`. * * Examples: * * Pass the middleware to Connect, grabbing .coffee files from this directory * and saving .js files to _./public_. Also supplying our custom `compile` function. * * Following that we have a `static` layer setup to serve the .js * files generated by Less. * * var server = connect.createServer( * coffee.middleware({ * src: __dirname + '/public' * }) * , connect.static(__dirname + '/public') * ); * * @param {Object} options * @return {Function} * @api public */ module.exports = coffee.middleware = function(options){ var regex = { handle: /\.js$/ }; options = options || {}; // Accept src/dest dir if ('string' === typeof options) { options = { src: options }; } // Only log if in debug mode var log = function(key, val, type) { if(options.debug || type === 'error') { switch(type) { case 'log': case 'info': case 'error': case 'warn': break; default: type = 'log'; } console[type](' \033[90m%s :\033[0m \033[36m%s\033[0m', key, val); } }; var coffeeError = function(err) { log("COFFEE " + err.name, err.message, 'error'); }; // Once option options.once = options.once || false; // Bare option options.bare = options.bare || false; // Encode CoffeeScript source file option options.encodeSrc = options.encodeSrc === undefined ? true : options.encodeSrc; // Source dir required var src = options.src; if (!src) { throw new Error('coffee.middleware() requires "src" directory'); } // Default compile callback options.compile = options.compile || coffee.compile; // Middleware return function(req, res, next) { if ('GET' != req.method.toUpperCase() && 'HEAD' != req.method.toUpperCase()) { return next(); } var pathname = url.parse(req.url).pathname; // Only handle the matching files if (regex.handle.test(pathname)) { if (options.prefix && 0 === pathname.indexOf(options.prefix)) { pathname = pathname.substring(options.prefix.length); } var coffeePath = path.join(src, pathname.replace('.js', '.coffee')); if (/^\.\./.test(path.relative(src, coffeePath))) { return res.send(403); // Forbidden } log('source', coffeePath); // Ignore ENOENT to fall through as 404 var error = function(err) { return next('ENOENT' == err.code ? null : err); }; // Compile coffee var compile = function() { log('read', coffeePath); fs.readFile(coffeePath, 'utf8', function(err, str){ if (err) { return error(err); } try { // Compile the JS to Coffee var opts = { bare: options.bare, filename: path.basename(coffeePath), sourceMap: true } var compiledObj = options.compile(str, opts); log('render', coffeePath); // Create own source map object (the other seems frozen) var sourceMapObj = { version: 3, file: path.basename(pathname), sources: [path.basename(coffeePath)], names: [], mappings: JSON.parse(compiledObj.v3SourceMap).mappings }; var compiledJs = compiledObj.js; // Optionally, append the comment to our source if (options.encodeSrc) { // Translate the sourcemap into a base64 comment var sourceMapStr = convertSourceMap.fromObject(sourceMapObj).toComment(); compiledJs += '\n' + sourceMapStr; } // Specify the type as JS and send back content res.type('js'); res.send(compiledJs); } catch (err) { coffeeError(err); return next(err); } }); }; // Force if (options.force) { return compile(); } // Only check/recompile if it has not been done at before if (options.once) { return next(); } // Compile the files return compile(); } else { return next(); } }; };