Skip to content

Instantly share code, notes, and snippets.

@lukegalea
Created August 8, 2012 01:25
Show Gist options
  • Save lukegalea/3291214 to your computer and use it in GitHub Desktop.
Save lukegalea/3291214 to your computer and use it in GitHub Desktop.
JADE Embedded Handlebars Precompiler
// Renders JADE -> Handlebars, Precompiles and outputs a single template bundle
// For example, https://github.com/lukegalea/recur_in/blob/master/templates/task.jade
// Dependencies
var handlebars = require('handlebars');
var fs = require('fs');
var async = require('async');
var jade = require('jade');
var uglify = require('uglify-js');
var encoder = require('./encoder');
var findAssetsModified = require('./fileUtil').findAssetsModified;
var config = require('./config.js');
var logger = require('winston');
require('sugar');
// Constants
var TEMPLATE_DIR = __dirname + '/../templates';
var MUSTACHE_REGEX = /\{\{(.+?)\}\}/g;
// Utility functions
// Jade will put HTML entities in the embedded mustache. Decode that.
var unescapeMustache = function(string) {
return string.replace(MUSTACHE_REGEX, function(full, inner) {
return "{{" + encoder.htmlDecode(inner) + "}}";
});
};
// Performs the main operation of JADE compile, render, unescape and precompile
var processTemplateContents = function(filename, contents) {
var compileOptions = {
filename: filename
};
var compiledJade = jade.compile(contents.toString(), compileOptions);
var renderedJade = compiledJade({ config: config });
renderedJade = unescapeMustache(renderedJade);
return handlebars.precompile(renderedJade, { asString: true });
};
// Finds all the templates in the template dir, processes each and combines them into a single "T" global where the key === the filename
var compileAllTemplates = function(callback) {
logger.profile("compile templates");
var results = "T={};";
fs.readdir(TEMPLATE_DIR, function(err, entries) {
if(err) return callback(err);
var confirmIsFile = function(entry, callback) {
var filename = TEMPLATE_DIR + '/' + entry;
fs.stat(filename, function(err, stat) {
callback(stat.isFile());
});
};
async.filter(entries, confirmIsFile, function(files) {
if(err) return callback(err);
var compileFile = function(file, done) {
var filename = TEMPLATE_DIR + '/' + file;
fs.readFile(filename, function(err, contents) {
if(err) return done(err);
var compiled = processTemplateContents(filename, contents);
logger.debug("Compiling", file);
var name = file.split('.').first();
if(name === '') return done();
results = results + "\nT['" + name + "'] = Handlebars.template(" + compiled + ");";
done();
});
};
async.forEach(files, compileFile, function(err) {
if(err) return callback(err);
logger.profile("compile templates");
callback(null, results);
});
});
});
};
// Caching and handling recompile on change
var cachedTemplates;
var cachedTemplatesTime;
var cacheTemplates = function(templates) {
cachedTemplates = templates;
cachedTemplatesTime = new Date();
};
var sendTemplates = function(res, data, date) {
res.header('Cache-Control', 'public, max-age=0');
res.header('Last-Modified', cachedTemplatesTime.toUTCString());
res.header('Content-Type', 'text/javascript');
res.send(cachedTemplates);
};
var checkIfRecompileNeeded = function(req, res, next) {
findAssetsModified('templates', [], function(err, templateResults) {
if (err) return next(err);
if (templateResults.latestAssetMtime > cachedTemplatesTime) {
return next(); // Let the compiler handle this
} else {
sendTemplates(res);
}
});
};
var uglifyTemplates = function(compiledTemplates) {
var ast = uglify.parser.parse(compiledTemplates);
ast = uglify.uglify.ast_mangle(ast);
ast = uglify.uglify.ast_squeeze(ast);
return uglify.uglify.gen_code(ast);
};
// Cache middleware (will prevent getAll from being called)
exports.cachedTemplates = function(req, res, next) {
if (cachedTemplates) { // Check for cache
if (config.recompileTemplates) { // If configured to check timestamps and recompile
checkIfRecompileNeeded(req, res, next);
} else { // We aren't going to check timestamps
sendTemplates(res);
}
} else { // There is no cache yet, proceed to the route handler
next();
}
};
// Main Route
exports.getAll = function(req, res, next) {
compileAllTemplates(function(err, compiledTemplates) {
if(err) return next(err);
var output = config.uglifyTemplates ? uglifyTemplates(compiledTemplates) : compiledTemplates;
cacheTemplates(output);
sendTemplates(res);
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment