- Improved Language (CoffeeScript, or Dart, TypeScript, etc.)
- HTML Domain Specific Language (Haml, Jade, Slim, others)
- 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
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.