blade
Version:
Blade - HTML Template Compiler, inspired by Jade & Haml
203 lines (194 loc) • 6.58 kB
JavaScript
/** Blade Public API and Middleware
(c) Copyright 2012. Blake Miner. All rights reserved.
https://github.com/bminer/node-blade
http://www.blakeminer.com/
See the full license here:
https://raw.github.com/bminer/node-blade/master/LICENSE.txt
*/
var fs = require("fs"),
path = require("path"),
url = require("url"),
Compiler = require("./compiler"),
bladeutil = require("./util");
try {
exports.version = require("../package.json").version;
} catch(e) {}
exports.compile = compile;
exports.compileFile = compileFile;
exports.Compiler = Compiler;
exports.renderFile = exports.__express = renderFile;
exports.middleware = middleware;
exports.readRuntime = function() {
return fs.readFileSync(__dirname + "/runtime.js");
}
//Cache of compiled template functions
var cache = {};
function compile(string, options, cb) {
if(typeof options == "function")
cb = options, options = {};
if(options.filename)
options.filename = path.resolve(options.filename);
if(options.cache)
{
if(!options.filename)
return cb(new Error("The `filename` option is required for caching"));
else
{
var cacheKey = {};
["filename", "minify", "includeSource", "selfClosingTags",
"templateNamespace", "basedir", "middleware"].forEach(function(opt) {
if(options[opt] != null)
cacheKey[opt] = options[opt];
}
);
cacheKey = JSON.stringify(cacheKey);
}
}
if(cacheKey && cache[cacheKey])
cb(null, cache[cacheKey]);
else
{
var compiler = new Compiler(string, options);
compiler.compile(function(err, tmpl) {
if(err) return cb(err);
if(cacheKey)
cache[cacheKey] = tmpl;
cb(null, tmpl);
});
}
}
function compileFile(filename, opts, cb) {
if(typeof opts == "function")
cb = opts, opts = {};
//make shallow copy of opts and add `filename` property
var options = {};
for(var i in opts)
options[i] = opts[i];
options.filename = path.resolve(filename);
//Check cache first
if(options.cache && cache[options.filename])
cb(null, cache[options.filename]);
else if(options.synchronous)
{
try {
var data = fs.readFileSync(filename);
} catch(err) {return cb(err);}
compile(data.toString(), options, cb);
}
else
fs.readFile(filename, function(err, data) {
if(err) return cb(err);
compile(data.toString(), options, cb);
});
}
function renderFile(filename, options, cb) {
compileFile(filename, options, function(err, tmpl) {
if(err) return cb(err);
//Make a copy of the options to be passed to the view, excluding reserved variables
var locals = {};
var reserved = ["locals", "cb", options.templateNamespace || "__"];
for(var i in options)
if(reserved.indexOf(i) < 0)
locals[i] = options[i];
tmpl(locals, cb);
});
}
/* Provides a nice Express middleware for compiling client-side templates
and delivering them to the client. Weak caching is used by default.
*/
function middleware(sourcePath, options) {
options = options || {};
options.mount = options.mount || "/views/";
if(typeof options.runtimeMount == "undefined")
options.runtimeMount = "/blade/blade.js";
if(typeof options.pluginsMount == "undefined")
options.pluginsMount = "/blade/plugins/";
if(typeof options.returnErrors == "undefined")
options.returnErrors = process.env.NODE_ENV == "development";
if(options.compileOptions == null)
options.compileOptions = {
"cache": process.env.NODE_ENV == "production",
"minify": process.env.NODE_ENV == "production",
"includeSource": process.env.NODE_ENV == "development"
};
sourcePath = path.resolve(sourcePath);
options.compileOptions.basedir = sourcePath;
options.compileOptions.middleware = true;
var fileCache = {}, pluginPath = path.resolve(__dirname + "/../plugins/");
function staticAsset(req, path) {
//support for node-static-asset
if(typeof req.assetFingerprint == "function" && exports.version)
{
var expires = new Date();
expires.setFullYear(expires.getFullYear() + 1);
req.assetFingerprint(path, path + "?v=" + exports.version,
{"etag": exports.version, "expires": expires});
}
}
return function(req, res, next) {
var pathname = url.parse(req.url).pathname;
if(options.runtimeMount && pathname == options.runtimeMount)
{
if(fileCache["runtime"])
res.type("application/javascript").send(fileCache["runtime"]);
else
fs.readFile(__dirname + "/runtime.js", function(err, data) {
if(err) return next(err);
data = bladeutil.uglify(data.toString(), true);
fileCache["runtime"] = data;
staticAsset(req, pathname);
res.type("application/javascript").send(data);
});
}
else if(options.pluginsMount &&
pathname.substr(0, options.pluginsMount.length) == options.pluginsMount)
{
var filename = path.normalize(pathname.substr(options.pluginsMount.length) );
var fullPath = path.join(pluginPath, filename);
//fullPath may contain /../../../etc/passwd ... no good
if(fullPath.indexOf(pluginPath) != 0)
res.type("text/plain").send("403 Forbidden", 403); //malicious filename
else if(fileCache[fullPath])
res.type("application/javascript").send(fileCache[fullPath]);
else
fs.readFile(fullPath, function(err, data) {
if(err) return next(err);
data = bladeutil.uglify(data.toString(), true);
fileCache[fullPath] = data;
staticAsset(req, pathname);
res.type("application/javascript").send(data);
});
}
else if(pathname.substr(0, options.mount.length) == options.mount)
{
var filename = path.normalize(pathname.substr(options.mount.length) );
var fullPath = path.join(sourcePath, filename);
//fullPath may contain /../../../etc/passwd ... no good
if(fullPath.indexOf(sourcePath) != 0)
return res.type("text/plain").send("403 Forbidden", 403); //malicious filename
compileFile(fullPath, options.compileOptions, function(err, tmpl) {
res.type("application/javascript");
var viewName = JSON.stringify(filename.replace(path.sep, "/") );
if(err)
{
if(options.returnErrors)
res.send("var e=blade._cb[" + viewName + "];e&&e(" +
"new Error(" + JSON.stringify(err.message) + "));");
else
next(err);
}
else
{
var deps = [];
for(var i = 0; i < tmpl.dependencies.length; i++)
deps[i] = tmpl.dependencies[i].replace(path.sep, "/");
res.send("blade._cachedViews[" + viewName + "]=" + tmpl.toString() +
";var e=blade._cb[" + viewName + "];e&&e(null," +
JSON.stringify(tmpl.reldir.replace(path.sep, "/") ) + "," +
JSON.stringify(deps) + "," + tmpl.unknownDependencies + ");");
}
});
}
else next();
};
}