Skip to content

Instantly share code, notes, and snippets.

@STRd6
Last active August 29, 2015 14:02
Show Gist options
  • Save STRd6/d2c147f45410cdcb0246 to your computer and use it in GitHub Desktop.
Save STRd6/d2c147f45410cdcb0246 to your computer and use it in GitHub Desktop.
Hamlet Implementation

Hamlet Implementation

There are many existing frameworks and libraries in JavaScript that handle data-binding and application abstractions but none of them offer an integrated solution that works with hihger level languages (CoffeeScript, Haml). You could come close with CoffeeScript + hamlc + Knockout or something similar but it would always be a bit of a kludge because they were never designed to work together.

There are three major issues that should be solved in clientside JavaScript applications:

  1. Improved Language (CoffeeScript, or Dart, TypeScript, etc.)
  2. HTML Domain Specific Language (Haml, Jade, Slim, others)
  3. Data-Binding (React, Knockout, others)

Hamlet is novel in that it provides a clean combined solution to these three issues. By building off of CoffeeScript in the compiler we get to have the same improved language inside and outside of our templates. haml-coffee provides a CoffeeScript aware HTML DSL, Knockout.js provides data-binding aware HTML, but no tool provided them together. What if we could truly have the best of both worlds?

%button(click=@say) Hello
%input(value=@value)
say: ->
  alert @value()
value: Observable "Hamlet"

This simple example demonstrates the power and simplicity of Hamlet. The value in the input field and the model stay in sync thanks to the Observable function. The template runtime is aware that some values may be observable, and when it finds one it sets up the bindings for you.

All of this fits within our < 4k runtime. The way we are able to achieve this is by having a compile step. Programmers accustomed to languages like Haml, Sass, and CoffeeScript (or insert your favorites here) are comfortable with a build step. Even plain JS/HTML developers use a build step for linters, testing, and minification. So granted that most web developers today are using a build step, why not make it do as much of the dirty work as we can?

The Hamlet compiler works together with the Hamlet runtime so that your data-bindings stay up to date automatically. By leveraging the power of the document object itself we can create elements and directly attach the events that are needed to observe changes in our data model. For input elements we can observe changes that would update the model. This is all possible because our compiler generates a simple linear list of instructions such as:

create a node
bind an id attribute to model.id
add a child node
...

As the runtime executes instructions it has the data that should be bound. Because the runtime is "Observable aware" it will automatically attach listeners as needed to keep the attribute or value in sync with the model.

Let's follow the journey of our humble template.

             parser    compiler   browser+runtime
              |          |              |
haml template -> JSON IR -> JS function -> Interactive DOM Elements

The template starts out as a text string. This gets fed into the hamlet-cli which converts it into a JS function. When the JS function is invoked with a model, in the context of the browser and the Hamlet runtime, it produces a Node or DocumentFragment containing interactive data-bound HTML elements. This result may then be inserted into the DOM.

The parser is generated from jison. We use a simple lexer and a fairly readable DSL for the grammar.

There's no strong reason to choose Haml over Slim or Jade, I just started with it because it was a syntax I knew well. The name Hamlet comes from "Little Haml" as it is a simplified subset of Haml. Adding support for a Jade or Slim style is as easy as creating a new lexer that with the appropriate subset of Jade or Slim.

Some of the simplifications to the language come from the power of the runtime to build DOM elements directly. We don't need to worry about escaping because we're building DOM elements and not strings. We can also avoid the DOCTYPE stuff and other server specific requirements that are not relevant to a clientside environment. Other reductions were chosen solely to make the language simpler, which has value in itself.

The core goal of Hamlet is to provide an HTML domain specific language that seamlessly inter-operates with CoffeeScript and provides bi-directional data binding. Each piece works together to provide an amazing overall experience. But you don't have to take my word for it, try it out for yourself with our interactive demos.

Other "Templating" libraries go to a lot of effort to just concatenate and interpolate strings, spitting out a big string of HTML at the end. The runtime then needs to convert this string into HTML and do stuff with it. Why go to all that work of parsing ids, attributes, buffered output, etc., just to throw it all away and force the browser to parse it again? Additionally the clientside code would need to set up bindings and need to re-traverse the DOM to attach to various nodes. All in all a lot of the work is wasted and works against other parts of the application.

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