Skip to content

Instantly share code, notes, and snippets.

@wthit56
Last active August 29, 2015 14:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wthit56/cca1169f73c9a3839396 to your computer and use it in GitHub Desktop.
Save wthit56/cca1169f73c9a3839396 to your computer and use it in GitHub Desktop.
Writing Your Own Non-Templating Engine

Writing Your Own Non-Templating Engine

Templating engines are a dime-a-dozen right now, ranging from micro-DSLs to opinionated behemoth frameworks. It can be a bit of a chore to wade through them all (even with a Template Chooser), and even when you settle on one to use, it may not have all the functionality you want. So what do you do? You write your own, with as many custom and customizable features as you wish.

But writing a templating engine can be a whole mess of a problem all by itself. Writing a custom DSL that won't break with a mis-placed space or capital letter, and does what you want it to do, and ready for future changes, improvements, and additions, is just a pain the behind.

So what would the ideal-world, blue-sky apex of templating engines look like? What would you be able to do with it? If we're talking about JavaScript and HTML (and in this article, for example's sake, I will be), you'd want to be able to use all the power and simplicity of both languages:

<h1>this.title</h1>
<ul>
	this.items.map(function(item) {
		return <li>
			<a href="item.href">item.title</a>
			(item.progress \ item.total)
		</li>;
	}).join("")
</ul>

The problem is, simply writing this way doesn't actually do anything useful. If viewed as a HTML page, the JavaScript will just be plain text. If run as JavaScript, the HTML will throw errors left right and centre. Now, JavaScript being the more programmatically expressive of the two languages involved (it can actually do stuff dynamically), that should be the top-level, "parent" environment. (A lot of templating languages use HTML as their top-level language, but this means the user can only use the specific parts of JavaScript allowed by the language. Doing it this way round means all of JavaScript is available to the user, and so is all of HTML.) So now the problem becomes what to do with the HTML.

JavaScript can work with HTML quite easily as text. But simply sticking quotes around a block of HTML won't work in a number of common edge-cases. The following code has *s where the code will break and throw errors:

"<h1>this.title</h1>*
<ul>" +
	this.items.map(function(item) {
		return "<li>" +
			"<a href="*"+ item.href + "">" + item.title + "</a> " +
			"(" + item.progress + " \ " + item.total + ")" +
		"</li>";
	}).join("") +
"</ul>"
  • new lines will break the string.
  • double-quotes will end the string early (or single-quotes if you startedwrapped the string with single-quotes).
  • back-slashes will escape the next character (or characters). Sometimes this won't do anything, sometimes this will throw an error, and sometimes this will insert whole new characters (\n becomes a new-line, \251 becomes the copyright symbol, etc.)!

We just want the HTML content to act as an inert, normal string, with no changes made or errors thrown. So simply wrapping the HTML in quotes won't be sufficient. We'll need to capture that HTML content somehow, and re-write it so that we can be sure nothing unexpected will happen. For now, we'll assume the JavaScript (and HTML bits) is just one big string that we can look at and mess with. In the real world, this could be in a file, in a <script type="something other than text/javascript"> tag, or even in a simple function (that we could .toString() to read the code from).

So now we need to find the HTML snippets from the code. And what do we use to find patterns in strings? Regex. (You could do all this with loops and .indexOf() and so on if you don't yet have a handle on Regex, but I'll explain the basics of what's going on anyway, so you can figure out how to code that up if you like.)

If we wanted to be as seamless as possible, we could conceivably write a Regex to find any HTML in our JavaScript (letting us use the first code example as-is), though this gets pretty hairy if we want to do anything beyond whole tags (<opening> and </closing>). You're free to try, but we're just going to skip that headache altogether. We're going to add just the tiniest smidge of markup to let us more easily pick out the bits we need to re-write. This really simplifies the Regex we need to write, and will let us code more versatile templates in the future.

So let's start with something really straightforward, double curly braces (this is kind of the inverse of the Handlebars templating engine). After adding these, our template looks something like:

{{<h1>}} + this.title + {{</h1>
<ul>}} +
	this.items.map(function(item) {
		return {{<li>
			<a href="}} + item.href + {{">}} + item.title + {{</a>
			(}} + item.progress + {{ \ }} + item.total + {{)
		</li>}};
	}).join("")
+ {{</ul>}}

Of course, you can use any kind of markers to show "HTML starts" and "HTML ends" in your templating engine. I'm partial to using "/" and "/" (or a variation thereof), myself, as it's nicely syntax-highlighted in most editors. But you could use absolutely anything you like, as long as you're sure it won't be used within an HTML block or in your regular JavaScript code (angle-brackets wouln't work, for example, as they would be used through the HTML and JavaScript code).

So what kind of beasty Regex would we need to parse all this? /\{\{([\W\w]*?)}}/g. That's it. The bit that comes before, any length of text (the HTML) that comes after, until it hits the bit that comes after. All we need to do is replace whatever is matched with the suitably-escaped version:

code.replace(/\{\{([\W\w]*?)}}/g, function(match, HTML) {
	return "\"" +
		HTML.replace(/[\\"]/g, "\\$&") // fix back-slash escaping and double-quotes
			.replace(/\n/g, "\\n") // fix new lines
	"\"";
});

NOTE: This is just a quick example, not necessarily adhering to best practices and so on. You can write it in your own style, with your own flare, even in Coffeescript if you like! ;P

So what does our code look like after?

"<h1>" + this.title + "</h1>\n<ul>" +
	this.items.map(function(item) {
		return "<li>\n			<a href=\"" + item.href + "\">" + item.title + "</a>\n			(" + item.progress + " \\ " + item.total + ")\n		</li>";
	}).join("")
+ "</ul>"

Not the prettiest code you've seen in your life, but it works exactly as expected every time. Just stick a "return" at the start, and you've got yourself a function, ready to go, no re-compilation needed, and generally super-fast, as we're just working with regular strings. You can even use a regular string in the midst of all this and it'll work just fine. Or make a function call, or loop over stuff with an IIFE, or do anything you can with JavaScript. All in a 6-line compiler.

If you want some extra reading on how to use this kind of templating engine, you can read Using Your Own Non-Templating Engine. It doesn't have anything too revelationary, but if you want some ideas on how to use these ideas, you may find it useful.

PS-- I know, I know. This is, by all definitions, a "templating engine". But it's not as grandiose as all that. It's more of a "JavaScript pre-processor". Or a "transpiler", maybe? But that sounds like ASTs and tree-crawling and all kinds of hellish drudgery. This method of templating is just way simpler than all that. It's not even a "micro-library", as there's no convenience methods or particular funcionality about it.

This is just 6 lines of code we're talking about. It can be written however you like, with whatever extras you feel like adding for your individual use. It can be customised, re-written, added to or taken away from. I wrote this article to show how I did it, and to invite anyone so-inclined to just go ahead and try doing it themselves. Have fun with it. This is JavaScript! ;P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment