a fully async text and HTML templating library using GorillaScript. Express compliant.
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<% block header %>
<% block content %>
<footer>
<% block footer: %>
<ul>
<%-- This is a template comment that will not be rendered --%>
<% for item in footer-items: %>
<li><a href="<%= item.url %>"><%= item.name %></a></li>
<% else: %>
<li>-</li>
<% end %>
</ul>
<% end %>
</footer>
<%-- the <%@ and @%> coming up mean to treat the internals as literal, no template magic. --%>
<script id="egs-hello" type="text/egs-template"><%@
Hello, <%= name %=>
@%></script>
</body>
</html>
var EGS = require('egs');
// the following both essentially do the same thing in different ways
EGS.renderFile("index.egs", { footerItems: [{ url: "/copyright", name: "Copyright (c) 2013" }] })
.then(
function (html) {
sendToClient(html);
},
function (err) {
handleError(err);
});
var indexTemplate = EGS.fromFile("index.egs", { extraOptions: "here" });
indexTemplate({ footerItems: [{ url: "/copyright", name: "Copyright (c) 2013" }] })
.then(
function (html) {
sendToClient(html);
},
function (err) {
handleError(err);
});
or with literal EGS code:
var EGS = require('egs');
EGS.render("Hello, <%= name %>!", { name: "world" })
.then(
function (html) {
sendToClient(html);
},
function (err) {
handleError(err);
});
var indexTemplate = EGS("Hello, <%= name %>!", { extraOptions: "here" });
indexTemplate({ name: "world" })
.then(
function (html) {
sendToClient(html);
},
function (err) {
handleError(err);
});
All functions return Promises/A+-compliant promises or functions which return such promises.
var express = require('express');
var app = express();
// the following line is unnecessary if you don't want to supply any global options
app.engine("egs", require('egs').express({ options: "here" }));
app.get('/', function (req, res) {
res.render('index.egs', { name: "friend" });
});
app.listen(3000);
console.log('Listening on port 3000');
EGS provides the standard .__express
function, so simply requesting a template with the extension .egs will work. If you wish to provide any custom global options such as custom tag tokens, then it just takes one line.
<%= some-variable %>
With the default escaper, if a value has a .toHTML()
method, that will be invoked and used as the escaped text. Otherwise, it will escape it for HTML.
<%=h some-variable %>
As you might suspect, h
(an alias for html
) is simply a helper function that wraps the variable in a box with a .toHTML()
method. Well, that’s a half-truth: an optimizer has a special case to turn all escape(h(value))
s into value
s.
<script>var x = "<%=j some-variable %>";</script>
The j
helper (an alias for javascript
) escapes text to be placed inside of a JavaScript string, turning "\r"
into "\\r"
and similar.
<% let items = ["hello", 'there', \friend] %>
<% for item in items: %>
<%= item %>
<% end %>
<% if user.authenticated: %>
<% partial "user" %>
<% else: %>
<% partial "auth" %>
<% end %>
Templates can specify a layout to extend from, which is simply just another .egs file. Layouts have no problem inheriting from their own super-layouts as well.
Layouts can specify blocks of content which can be filled out by templates inheriting from the layout, but can have their own placeholder in case they aren’t.
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
</head>
<body>
<% block content %>
</body>
</html>
<% extends "layout" %>
Any text outside of a block is ignored.
<% block content: %>
<% block nav %>
<% block main: %>
Better fill this with something!
<% end %>
>% block footer %>
<% end %gt;
<% extends "main" %>
<% block nav: >
<nav>
<a href="/somewhere-cool">Somewhere cool</a>
</nav>
<% end %gt;
<% block main: %>
<p>Welcome!</p>
<% end %>
Partials are a way to segment different portions of your template into reusable chunks, and is highly recommended to use.
Requesting a partial by default prefixes the filename with "_"
, but that can be overridden with the partialPrefix
option.
<form action="/auth" method="post">
<label>Username: <input name="username"></label>
<label>Password: <input name="password" type="password"></label>
</form>
<p>
Welcome, <%= username %>!
<a href="/logout">Logout>
</p>
<% extends "layout" %>
<% block user: >
<% if user.is-logged-in: %>
<%-- all context variables, including `user`, from this context are passed in automatically as well. --%>
<% partial "user", username: user.username >
<% else: %>
<% partial "auth" >
<% end %>
<% end %>
EGS piggy-backs on top of GorillaScript's amazing Promise support, and inside any .egs templates, one can yield
a Promise to suspend the current rendering, wait for the result (without blocking), and resume rendering once the result is received.
In fact, the partials and extends support use promises in its backend to asynchronously fetch the requested file, compile to a template, then run.
Here's some starting code
Started at <%= Date() %>
And we're about to wait...
<% yield delay! 1000_ms %>
And we're done, without blocking the rest of the app!
Or if we want to request actual information:
<% let data = yield gimme-some-data(50_items, timeout: 200_ms) %>
And now the `data` variable is full of juicy information
<%= data %>
Going to be at least 1 second more than the first time:
Done at <%= Date() %>
Since EGS supports asynchrony well, it is able to periodically send chunks of text instead of waiting until the end. If you check out the Try it out link at the bottom of the page, you can see this in action.
var http = require('http');
var egs = require('egs');
http.createServer(function (req, res) {
res.writeHead(200, {
"Content-Type": "text/html"
});
egs.renderFileStream(__dirname + "/views/my-template.egs")
.on("data", function (chunk) {
res.write(chunk);
})
.on("error", function (err) {
res.write("oh no, we got an error!");
logError(err);
})
.on("end", function () {
res.close();
});
}).listen(3000, "127.0.0.1");
All render
methods have equivalent renderStream
variants that return a Stream
rather than a Promise<String>
.
A Stream
is simply an Object
with an on
method, used as above. The only events are "data"
, "error"
, and "end"
. As soon as either "error"
or "end"
are emitted, no other events will occur.
Although it is recommended to use EGS' default tokens, you have a lot of customizability for them.
By default, the following options are passed to the compiler:
{
open: "<%",
close: "%>",
openWrite: "<%=",
closeWrite: "%>",
openComment: "<%--",
closeComment: "--%>",
openLiteral: "<%@",
closeLiteral: "@%>",
}
Another typical set of options would be:
{
open: "{%",
close: "%}",
openWrite: "{{",
closeWrite: "}}",
openComment: "{#", // or "{*"
closeComment: "#}", // or "*}"
openLiteral: "{@",
closeLiteral: "@}",
}
To make your template look like so:
Hello, {{ name }}
{% if been-good: %}
Well, you deserve a cookie.
{% else: %}
{# this guy totally doesn't deserve a cookie #}
Try harder next time.
{% end %}
There is a command-line egs binary that can be used to generate packages or to render templates ad-hoc. Using egs -p "folder/path", you can very easily package up a whole folder of .egs files into a single .js file. You can even choose to add coverage or SourceMap support.
The recommended way to automatically build EGS packages is through Grunt. It is easy to add EGS support with the grunt-egs plugin.
Here is a very simple example of how to a folder of .egs files into a single .js file:
grunt.initConfig({
gorilla: {
dist: {
options: {
sourceMap: true,
export: "MyAppViews"
}
files: {
"lib/views.js": ["src/views"]
}
}
}
});
There are two files for browser-specific handling of EGS.
In production, it is recommended to only use egs-runtime.js, as any templates should be able to be precompiled.
If you would like to contribute to this list, please file an issue.
IRC is a way to chat with other GorillaScript developers, likely including the author (depending on) and other contributors. Feel free to join us at #gorillascript on irc.freenode.net.