Skip to content

Instantly share code, notes, and snippets.

@KrofDrakula
Last active October 3, 2015 06:01
Show Gist options
  • Save KrofDrakula/aaf4ff76462ea952b564 to your computer and use it in GitHub Desktop.
Save KrofDrakula/aaf4ff76462ea952b564 to your computer and use it in GitHub Desktop.
An easily understandable templating function
// A very light-weight templating function
// Use <% ... %> to embed code, <%= ... %> to output expressions.
//
// Caveat: because the parser makes no attempts at interpreting
// any `<%` and `%>` inside code blocks, so if you need
// metatemplating, you need to escape those sequences
// just like '<scr'+'ipt>' in script tags.
//
// See examples of usage below.
function template(str, params) {
var fn, fragments, body, insideExpression;
if (str.indexOf('<%') == -1) {
// there are no template markers, so no need to parse
fn = function() { return str; };
} else {
// we need to parse the template, so split it into chunks
// of interleaved literals and code blocks
fragments = str.split(/<%\s*|\s*%>/g),
// we wrap the body of the function in a `with` statement
// to simulate local variables for the template
body = 'var p = []; with(o) {\n',
// the split we've performed above means we'll always start
// with at least an empty string of a literal ('<% ...'),
// this flag will help us decide what to do
insideExpression = false;
// loop through each fragment and process it
fragments.forEach(function(frag) {
if (insideExpression) {
if (frag[0] == '=') {
// it's an output expression, so output its value; the
// .replace() call here just gets rid of any whitespace
// surrounding the expression in the generated code
body += " p.push(" + frag.replace(/^=\s*|\s*$/g, '') + ");\n";
} else {
// it's a JavaScript statement, so append it to the body
body += " " + frag + '\n';
}
} else if (frag) {
// literal string, just escape the sequence
body += generateOutput(frag);
}
// the next fragment will be the opposite of the currently
// processed fragment, so flip the boolean value
insideExpression = !insideExpression;
});
// close the `with` statement and stitch the strings together to
// compile the final result
body += '} return p.join("");';
try {
// create the new function with a single parameter to pass in
// the template objects
fn = new Function('o', body);
} catch(ex) {
// catch any parsing errors and throw a new Error object containing
// the function body
var err = new Error('Cannot parse template! (see `template` property)');
err.template = body;
throw err;
}
}
// if any parameters were passed into the function, execute the
// template function, else return the generated function
return params ? fn(params) : fn;
// this is a helper function that slices up a template literal along newlines into
// separate `push` commands for easier debugging
function generateOutput(str) {
return " p.push('" + str.replace(/'/g, "\\'").split('\n').join("\\n');\n p.push('") + "');\n";
}
}
// create a cacheable template function
var a = template('My name is <%= name %>.');
// 'My name is Krof.'
console.log(a( { name: 'Krof' } ));
// 'My name is Bond. James Bond.'
console.log(a( { name: 'Bond. James Bond' } ));
// create and execute a template in a single step
// trick: you can leave out the terminating tag!
var b = template('1 + 2 = <%= result', { result: 3 });
// '1 + 3 = 3'
console.log(b);
// run some code inside the template
var c = template('<% var stripped = message.replace(/^\\s+|\\s+$/g, ""); %>You said: "<%= stripped %>"');
// 'You said: "SO MUCH SPAAAAACE!!!!1"'
console.log(c( { message: ' SO MUCH SPAAAAACE!!!!1 ' }));
// what about compilation errors in the templates?
// *HINT*: inspect the Error in the dev tools,
// it contains the `template` property
var d = template('<% this is not valid JS %>');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment