Last active
October 3, 2015 06:01
-
-
Save KrofDrakula/aaf4ff76462ea952b564 to your computer and use it in GitHub Desktop.
An easily understandable templating function
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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