Grenade
A template engine for Node and Express, influenced by Taylor Otwell's "Blade" template engine for Laravel.
Why do we need another template engine?
We don't. I just wanted more flexibility with my template engine.
This was designed to work with the server and supports a number of features:
- Natively supports Layouts.
- Escaped variables, raw variables, or custom variable output.
- Ability to call javascript functions and helpers, like EJS.
- Ability to extend with custom tags.
- Ability to write custom filter functions, like Angular.
- Plugs easily into Express.
- Easy to write and read.
- Ability to precompile templates (coming soon).
- Promise-based rendering, necessary for building UI components that rely on data.
- Use your own delimiters, such as
{{ }}
or${ }
.
Installing
npm install grenade --save
Usage
Compiling from a file:
var grenade = ; // All the available options.var options = // The root view path. Views are loaded relative to this directory. rootPath: __dirname+"/views/" // The path of your Component javascript classes. componentPath: __dirname+"/components/" // The file extension. extension: 'htm' // Pretty print the output? Useful for debugging. prettyPrint: false //Uses js-beautify module. prettyPrintOptions: {} // Your variables are prepended with this. ie, ${data.varName} localsName: "data" // Enable template caching? True for production mode. enableCache: false // Use promises for rendering? // This could make rendering slower, but allows more flexibility. promises: true // This is a regex. Change to whatever. delimiters: grenadeutilsDELIM_HANDLEBARS //delimiters: grenade.utils.DELIM_JAVASCRIPT; // This will load and compile the file "./views/content.htm";grenade;
Using with Express:
var grenade = ;var express = ; var app = ; // This will set up the view engine.grenade;
Syntax
Grenade's basic syntax looks very similar to Laravel's Blade syntax.
Tags are denoted with a @
and have custom functionality. Variables are wrapped by whichever delimiters you specify.
<!-- ./views/index.frag -->@extends(layouts/master) @section(head) ${#This is a comment. And it's in the head section of my layout.} @endsection @section(body) @include(common/navigation) This is in my content section.@endsection
<!-- ./views/layouts/master.frag --> ${=title} @yield(head) @yield(body)
Grenade compiles into pure javascript, which increases performance. For example:
This gets compiled to javascript, ${ data.buddy }!@if(true)I promise.@endif
Compiles into:
__out;__out;__out;iftrue __out;return $$qall__out;
Variables
By default, variables have the javascript-esque syntax: ${variable}
. You can, however, use other delimiters if you so desire.
${ data.variable }
Escaped value.${= data.variable }
Raw value, good for HTML output.${# data.variable }
Comment. Not processed when rendered.${: data.variable }
Returns the literal string,${ data.variable }
. Useful if using the handlebars syntax with Angular applications.
Functions
Grenade allows you to call functions from your data object, like EJS:
${ data.myFunction(arg1,arg2,"string") }
Control structures, loops, and tags
Grenade comes with all the basic control structures, and some cool ones.
@if
Display the contents of the block if the expression is truthy.
@if(condition) It's true.@elseif(condition) This condition is true.@else None are true.@endif
@unless
Displays the contents of the block if the expression is falsey.
@unless(condition) It's false.@else It's true.@endif
@for
Your basic for
loop, just like javascript.
@for(var i=0; i<=data.items.length; i++) Item ${ i }@endfor
@foreach
Loop through the given data array or hash object.
@foreach(item in items) ${ item.name }@endforeach @foreach([index,item] in items) ${ index } : ${ item }@endforeach
@include
Include a file at the given location, relative to the rootPath
.
@include(file/name)
@show
Shows the given strings if the expressions are truthy. Useful for creating classes.
<!-- Markup -->Item<!-- Rendered -->Item
This is pretty common, so a prefix filter also works here:
Item
@verbatim
Display the contents of the block exactly how it's shown.
@verbatim @if(false) This whole thing shows up, including the @if. @endif@endverbatim
@push and @stack
Collects the @push
blocks and renders them all under the @stack
tag. Useful for adding scripts or CSS links to the document head or footer.
@stack(links) @push(links)@endpush ... @push(links)@endpush
@set
Sets a variable. When compiled to plain javascript, it merely sets a var
by the specified name.
@set(name, "Hello World!") ${=name}
@with
When compiled to plain javascript, uses the with(object)
syntax.
@with(data) <!-- This is actually data.title --> ${= title }@endwith
@extends
Extend the current document off of a layout. Any include or component file can use it's own layout.
Works in conjunction with @yield
and @section
.
<!-- my-component.frag -->@extends(layouts/component) @section(body) The yield tag is filled in!@endsection
<!-- layouts/component.frag --> @yield(body)
@component and @block
This is a very special tag that allows you to create custom UI components off of Component classes.
A component has a javascript class associated with it, which you should define in whatever directory you
specified in componentPath
.
@component(MyComponent, {title:"Hello"}) <!-- or the shorthand... -->@cmp(MyComponent, {title:"Hello"})
A block is essentially the same as a component, only you can create a "transcluded" template that gets passed to your component.
@block(MyComponent, {title:"Hello"}) This HTML gets past to my component class. I can then print it using ${=data.$block} !!@endblock
The component declaration looks kind of like this:
// /componentPath/MyComponent.js"use strict"var grenade = ; Component { // The tag argument is the matching tag, including any passed args. // If using @block, a tag will have a tag.scope template. supertag; thisdata = title:"Default Title" // Use another grenade template file... thisview = "components/example"; // Or, use a string template. thistemplate = "<div> ${=data.$block} </div>"; } { // Maybe call to the server? Returning a promise is ok! // return Users.find().then(...); return super; } moduleexports = MyComponent;
Filters
Filters are used in much the same way as Angular. They can be defined like so:
var grenade = ; grenadeFilter;grenadeFilter;
In the markup, they are applied in order (comma-separated)
<!-- Markup -->${=data.title | toUpper,bold} <!-- Rendered -->TITLE
Prefix filters
Sometimes, filters or helpers are done so often, a custom prefix would be nice to have. One good example is using locales:
<!-- Instead of this... -->${toLocale(data.variable)}${toLocale("en_us.welcome")} <!-- Let's do this -->${>"en_us.welcome"}
To do this, add the following option when creating a filter:
grenadeFilter;
Note: Prefix filters are applied LAST by default, so be sure to include
the pushPrefix: false
in this case. So, our locale string is modified accordingly by other filters.
${>"en_us.welcome" | toUpper,bold}${>"en_us.welcome" | escape} <!-- This also works -->${"en_us.welcome" | toLocale,toUpper,bold}
Creating Custom Tags
Custom tags look like @tagname(args)
. You can add your own to create
custom UI components or control structures.
var grenade = ; // This special tag will only display it's enclosed scope if a user has the given role. grenadeTag
And so our markup can be:
@role(superuser) My user's role is a superuser!@endrole
Testing
mocha test
Notes
Why Not Use...
- EJS? Performance, no native layout support, includes are relative, and I hate writing closing braces
<% } %>
. - Handlebars? Doesn't play nice with Angular front-ends, layouts require plugin.
- Pug? Syntax is hard to read sometimes, can't copy/paste the source code into html files for use, and whitespace is annoying.
- Marko? Writing HTML tags as control structures or inlining control structures doesn't seem right, doesn't play nice with some IDE's.
Also, a common theme is lack of extensibility. I want to be able to extend like crazy.
Performance
Performance is definitely a concern, so templates are compiled to plain javascript and the compiled templates are cached by filename.
Benchmark testing has been done with Marko's handsome Templating benchmark suite.
What's with the name?
Evolution of a name. "Blade-for-node" -> "Node-Blade" -> "Nade" -> "Grenade".
Contributing
Let me know if you want to help out.