Embedded GorillaScript (EGS)

Fork on Github

a fully async text and HTML templating library using GorillaScript. Express compliant.

Table of contents

Features

Usage

Template

<!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>

JavaScript

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.

Syntax

Escaped output

<%= 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.

Unescaped output

<%=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 values.

Custom-escaped output

<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.

GorillaScript code

<% let items = ["hello", 'there', \friend] %>
<% for item in items: %>
  <%= item %>
<% end %>
<% if user.authenticated: %>
  <% partial "user" %>
<% else: %>
  <% partial "auth" %>
<% end %>

Inheritance

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.

layout.egs
<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
  </head>
  <body>
    <% block content %>
  </body>
</html>
main.egs
<% 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;
index.egs
<% extends "main" %>

<% block nav: >
  <nav>
    <a href="/somewhere-cool">Somewhere cool</a>
  </nav>
<% end %gt;
<% block main: %>
  <p>Welcome!</p>
<% end %>

Partials

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.

_auth.egs
<form action="/auth" method="post">
  <label>Username: <input name="username"></label>
  <label>Password: <input name="password" type="password"></label>
</form>
_user.egs
<p>
  Welcome, <%= username %>!
  <a href="/logout">Logout>
</p>
main.egs
<% 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 %>

Asynchrony

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() %>
    

Streams

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.

Customizable tokens

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 %}

Build support

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"]
      }
    }
  }
});

Browser support

There are two files for browser-specific handling of EGS.

  1. extras/egs-runtime.js: All you need to render precompiled template packages (built with egs -p).
  2. extras/egs.js: Allows in-browser compilation. Depends on extras/egs-runtime.js. Requires GorillaScript to be available.

In production, it is recommended to only use egs-runtime.js, as any templates should be able to be precompiled.

Code Editor/IDE support

If you would like to contribute to this list, please file an issue.

Chat with others (IRC)

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.