Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Vash, the 60 billion double-dollar template-maker. Razor syntax, for JavaScript templates
JavaScript HTML

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
bin
build
docs
src
support
test
.gitattributes
.gitignore
.npmignore
AUTHORS
CONTRIBUTING.md
LICENSE
README.md
package.json

README.md

Vash, the 60 billion double-dollar template-maker

Vash is an implementation of the Razor templating syntax in JavaScript. A cheat sheet can be found on Phil Haack's Blog. I call this a "template-maker" because it's not a framework, and it's not a templating engine. Vash does one thing, and one thing only: turn a string template into a compiled JS function!

Here's a quick example:

// compile template
var itemTpl = vash.compile( '<li data-description="@desc.name">This is item number: @number.</li>' );

// generate using some data 
var out = itemTpl({ desc: { name: 'templating functions seem to breed' }, number: 2 });

// dump it
console.log( out )

// outputs:
<li data-description="templating functions seem to breed">This is item number: 2.</li>

One more:

// assume this is in the DOM somewhere:
<ul>
@model.forEach(function(m){
    <li>@m.lastname, @m.firstname</li>
})
</ul>

// generate
var out = itemTpl([
    { firstname: 'Meryl', lastname: 'Stryfe' },
    { firstname: 'Milly', lastname: 'Thompson' }
]);

// outputs
<ul>
    <li>Stryfe, Meryl</li>
    <li>Thompson, Milly</li>
</ul>

There are many more examples in the unit tests, located in test/vash.test.js. Stand-alone examples are coming soon!

Neat Stuff

  • Works in Browser or in Node: Comes with built in Express support, and also works clientside in all browsers >= IE6 (!) and up (with the use of ES5 Shim for the array iteration methods).
  • HTML Escaping: As of version 0.4x, Vash automatically HTML encodes values generated by an explicit or implicit expression (e.g. interpolation).
  • Speed: Rendering with vash.config.useWith = false (see test/SPEED.txt, test/vash.speed.js for now) is "fast enough" :).
  • No dependencies: Vash itself has no external dependencies, aside from the ES5 array iteration methods (map, reduce, filter, etc), and Vows for testing.
  • Complete: Vash supports approximately 98% (this is a highly scientific estimate) of the actual Razor syntax, all the things you actually use. See below for what it doesn't support.

BUILD

support/build.js build && node test/vash.test.js && node test/vash.test.js min

Vash has a few builds for various purposes, found in the build folder. There are three primary use cases for Vash:

  1. Templates are compiled at runtime in the browser.
  2. Templates are compiled at build time server-side, and then compressed template functions are served to the browser.
  3. Templates are compiled and rendered in node, server-side, using something like Express.

Given these use cases, there are a few different builds optimized:

  • vash.js / vash.min.js: The full version of Vash, including all runtime helpers. Use this for scenario 1 and 3.
  • vash-runtime-all.js / vash-runtime-all.min: Vash's runtime, which currently includes HTML escaping and layout/view helpers. Use this for scenario 2.
  • vash-runtime.js / vash-runtime.min.js: the minimum amount of code required for a precompiled template to be able to execute client-side (currently HTML escaping). Use this for scenario 2, if you're trying to save code size and only need pure template-rendering functionality.

USAGE

vash.compile(templateString, [options])

Vash has one public method, vash.compile(). It accepts a string template, and returns a function that, when executed, returns a generated string template. The compiled function accepts one parameter, model, which is the object that should be used to populate the template.

options is an optional parameter that can be used to override the global vash.config for a single call to compile.

OPTIONS

vash.config.useWith = true/false(default)

If vash.config.useWith is set to true, then Vash will wrap a with block around the contents of the compiled function. Set to false, changes how you must write your templates, and is best explained with an example:

// vash.config.useWith == true
<li>@description</li>

vs

// vash.config.useWith == false
<li>@model.description</li>

Rendering is the same regardless:

compiledTpl( { description: 'I am a banana!' } );
// outputs:
// <li>I'm a banana!</li>

The default is false.

Tech note: using a with block comes at a severe performance penalty (at least 25x slower!). Using with is mostly to support the cleanest syntax as possible.

vash.config.htmlEscape = true(default)/false

As of version 0.4x, Vash now automatically HTML encodes values generated by an explicit or implicit expression. For example:

// template
<li>@model.description</li>

// content
For the > good!

// vash.config.htmlEscape = true (default)
// outputs
<li>For the &gt; good!</li>

// vash.config.htmlEscape = false
// outputs
<li>For the > good!</li>

To prevent this from happening on a single expression:

// template
<li>@html.raw(model.description)</li>

// content
For the > good!

// outputs
<li>For the > good!</li>

html.raw marks output as having been pre-encoded by wrapping it in a lightweight object that carries a toHtmlString method. Any object carrying a toHtmlString method is automatically converted to an encoded representation using this method when output into the rendered template by Vash.

vash.config.modelName = "model"

If vash.config.useWith is set to false, then this property is used to determine what the name of the default internal variable will be. Example:

// vash.config.useWith == false
<li>@model.description</li>

vs

// vash.config.useWith == false
// vash.config.modelName == 'whatwhat'
<li>@whatwhat.description</li>

Again, rendering is the same regardless:

compiledTpl( { description: 'I am a banana!' } );
// outputs:
// <li>I'm a banana!</li>

vash.config.helpersName = "html"

Determines the name of the internal variable through which registered helper methods can be reached. Example:

<li>@html.raw(model.description)</li>

vs

// vash.config.helpersName == "help";
<li>@help.raw(model.description)</li>

Again, rendering is the same regardless:

compiledTpl( { description : '<strong>Raw</strong> content!' } );
// outputs:
// <li><strong>Raw</strong> content!</li>

vash.config.debug = true/false(default)

Setting vash.config.debug to true will compile templates with extensive debugging information, so if an error is thrown while rendering a template, exact location (line, charater) information can be given.

The default is false, because while the performance implications are negligible, there is a noteable increase in the size of the compiled template functions.

vash.config.debug[Compiler/Parser] = true/false(default)

Setting either of these to true causes Vash to output extensive debugging information (parse tree, tokens, decompiled template function) to the console, useful mostly for Vash development.

vash.config.favorText = true/false(default)

When Vash encounters text that directly follows and opening brace of a block, it assumes that unless it encounters an HTML tag, the text is JS code. For example:

@it.forEach(function(a){
    var b = a; // vash assumes this line is code
})

When vash.config.favorText is set to true, Vash will instead assume that most things are non-code (markup/text) unless it's very explicit.

@it.forEach(function(a){
    var b = a; // vash.config.favorText assumes this line is content 
})

This option is EXPERIMENTAL, and should be treated as such. It allows Vash to be used in a context like Markdown, where HTML tags, which typically help Vash tell the difference between code and content, are rare.

CLI

As of v0.5.1, Vash now has a command-line utility. Install Vash globally:

npm install -g vash

To access it. Once installed, you can do things like:

// stdin, stdout
echo '<div>@model.name</div>' | vash

// file in, write to file in directory
vash -f mytemplatefile.html -o tpls/

// pass options to the vash compiler
echo '<div>@model.name</div>' | vash -j '{ "modelName": "it", "debug": true }'

// assign the compiled function to a namespace
echo '<div>@model.name</div>' | vash -t JST -p mytpl
// outputs: JST['mytpl']=anonymous(model){ ... }

And more!

Express Support

A basic example can be found in test/vash.express.js.

Vash as a View Engine

As of v0.5.1, Vash now offers runtime view engine support in the style of Jade's block/append/prepend and extends/include. This means that you can do:

// in index.vash
@html.extends('layout', function(model){

    @html.block('content', function(model){

        <h1 class="name">@model.location.name</h1>
        @html.include( 'hours', model.location.hours )
        <p class="menu"><a href="@model.location.menuLink">Menu</a></p>
        <p class="address">
            <a class='gmap' href="@model.location.gmapLink">
                <span class="street">@model.location.street</span><br />
                <span class="city">@model.location.city</span>,
                <span class="state">@model.location.state</span>
                <span class="zip">@model.location.zip</span>
            </a>
        </p>
    })

    @html.append('footer', function(){
        <p>Your Copyright Goes Here</p>
    })
})

// layout.vash
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>@model.title</title>
        <link rel="stylesheet" href="site.css" type="text/css" media="screen" charset="utf-8">
    </head>
    <body>      
        <section>
            @html.block('content')
        </section>  

        <footer>
            @html.block('footer')   
        </footer>
    </body>
</html>

This has been relatively untested in the browser. While it should in theory work (assuming all templates are available synchronously), it has not been thoroughly tested.

There are a few limitations currently. This all occurs at runtime, which is why callbacks are used instead of straight blocks. In the future this will hopefully happen at compile time, but that adds a lot of extraneous functionality deep into the core of Vash (things like loading files from the network/file system). Because of the scoping involved, all callbacks must have model defined as the first parameter. In the above examples, model is used because that is Vash's default. If vash.config.modelName were set to it, a block would be defined using it:

@html.block('content', function(it){
    <h1 class="name">@it.location.name</h1>
})

The "root-most" block definition must not have any content attached to it (e.g. a callback), as shown in the layout.vash file above. This is to allow Vash to know when a block should be overriden vs output. Again, this is happening at runtime; there is no way for Vash to currently know what the "root" template is or when all templates have been included. Therefore, if Vash encounters a previously defined block that has no content, it knows to finally output.

Errata

Since this is JavaScript and not C#, there are a few superfluous aspects of Razor that are not implemented, and most likely never will be. Here is a list of unimplemented Razor features:

@foreach

This is not a JavaScript keyword, and while some code generation could take place, it's more complex than I'd like to get. However, the following is a perfect substitute:

<ul>
@model.forEach(function(m){
    <li>@m.lastname, @m.firstname</li>
})
</ul>

@helper

helper implies that there are view lookups, auto-compiling, centralized view locations, etc, and this is bigger than Vash. However, templates can of course be called from within other templates, provided they are scoped properly, and you can do this too:

@function specialLink(url, text){
    <a href="@url" class="special-link">@text</a>
}

<p>This is my @specialLink(model.url, 'SPECIAL LINK!')</p>

@using

JS doesn't work this way...

Passing templates to inline function calls

Vash cannot do this:

@someHelperMethod('a parameter', @<a href="@url">@text</a>)

Current Test Results

support/build.js build && node test/vash.test.js
······································································································ 
✓ OK » 102 honored (0.182s) 

Why Vash?

The original name of this syntax is Razor, implying that it is as stripped down as possible (see Occam's Razor), and so a friend and I started riffing on it. Below is the stream of connected thoughts:

> razor...
> precision, surgical, steel
> tanto
> wakizashi
    > WK.tpl()
    > japanese emoticons?
    > _$8 is a valid JS identifier
    > mootools has $$ 
        > double dollars 
        > the 60 billion double dollar man 
            > Vash the Stampede! 
            > vash.tpl()
                > Very Awesome Scripted HTML
                > ...maybe just vash

TODO

  • refactor tests to take advantage of Vows' awesomeness

License

MIT

Copyright (C) 2012 by Andrew Petersen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Something went wrong with that request. Please try again.