A precompiled almost-HAML engine in C#


This project is still a work in progress, so this article serves as an introduction to the problem space and walks through how the code works.

In the past when I wrote different web applications, I used Ruby on Rails combined with the HAML template language. HAML is my favorite way to write HTML because it is an abstract representation of an HTML DOM combined with a hint of Python syntax.

Being an abstract representation means that it doesn’t have to directly correspond to what the resulting HTML looks like. This decoupling enables a HAML render engine to reorganize the code to cleaner and simpler.

Take a look at the following example:

%html{ lang: 'en' }
    %title Hello world!
    %a{ href: 'https://technowizardry.net' }= my_link
    %div{ b: 'abc', a: 'xyz' } testCode language: JavaScript (javascript)

In other template engines like Ruby’s ERB or C#’s Razor, the white space is preserved and whatever indention you add, is included in the output HTML.

<html lang="en">
    <title>Hello world!</title>
    <a href="https://technowizardry.net">Test</a>
    <div b="abc" a="xyz">test</div>

Indention can be handy when developing, but why waste the space when running production? One could just delete all the spacing in the source code and check this in, but now your code is harder to read. Can we have the best of both worlds?

The current state of the world

I’ve started experimenting with the new .net Core framework a lot because I like the framework and C# as a language. Unfortunately, HAML isn’t directly supported and instead the default render engine in ASP.NET MVC is just an low level HTML renderer which has the same problems as we highlighted above.

Instead I wanted to try to see if I can build my own solution and want to see how far we can push it with performance optimizations. Can we precompile the template into partial HTML streams? Can we optimize the HTML to be more friendly to Gzip? For example, if you have <a class="foo bar" /> and <a class="bar foo" /> Both of these elements are semantically equivalent and the classes can be ordered consistently so that Gzip can be efficiently compress them.

Fair warning, this will be prototype code and not ready for production quite yet.

Adding C# to the mix

I found a previous attempt at this called NHaml. There was quite a bit of work done on it, but it did not support .NET Core and seemed coupled to ASP.NET. I ended up borrowing the parsing logic (with modifications) and writing my own rendering engine.

But first, let’s see some results:

%html{ lang: 'en' }
    %title Hello world
    %meta{ charset: 'utf-8' }
    %meta{ content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0', name: 'viewport' }
    .page-wrap{ class: DateTime.Now.ToString("yyyy"), d: 'bar', a: 'foo' }
      = DateTime.Now.ToString("yyyy-mm-dd")
      %h1= new Random().Next().ToString()
      %p= model.ToString()
      - if (true)
        - if (1 > 0)
          %div really true
        %div Is True
      - else
        %div wat
      - if (false)
        %div Is False

Gets compiled into the following, then the cached class is called for following executions.

using System;
using System.IO;

internal sealed class __haml_UserCode_CompilationTarget
	private string model;

	public __haml_UserCode_CompilationTarget(string _modelType)
		this.model = _modelType;

	public void render(TextWriter textWriter)
		textWriter.Write("<!DOCTYPE html><html lang=\"en\"><head><title>Hello world</title><meta charset=\"utf-8\"/><meta content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0\" name=\"viewport\"/></head><body><div a=\"foo\" class=\"page-wrap ");
		textWriter.Write(" d=\"bar\" \">");
		textWriter.Write(new Random().Next().ToString());
		textWriter.Write("</p><div class=\"container content-pane\"/>");
		textWriter.Write("<div>really true</div>");
		textWriter.Write("<div>Is True</div>");
		textWriter.Write("</div><div class=\"in modal-backdrop\"/></body></html>");

Note how the runs of HTML that never changes is transformed into static strings and all elements are normalized consistently.

A walk through the code

The HamlView is the effective entry point. It checks to see if it has a cached copy of the template in memory, if not then it requests a compilation.

[ To Be continued]

Migrate Sprockets to Webpacker with React-rails

Ruby on Rails recently launched support for compiling static assets, such as JavaScript, using Webpack. Among other things, Webpack is more powerful at JS compilation when compared to the previous Rails default of Sprockets. Integration with Rails is provided by the Webpacker gem. Several features that I was interested in leveraging were tree shaking and support for the NPM package repository. With Sprockets, common JS libraries such as ReactJS had to be imported using Gems such as react-rails or classnames-rails. This added friction to adding new dependencies and upgrading to new versions of dependencies.

A couple of my projects used react-rails to render React components on the server-side using the legacy Sprockets system. This worked well, but I wanted to migrate to Webpacker to easily upgrade to the newest versions of React and React Bootstrap (previously I imported this using the reactbootstrap-rails, but this stopped being maintained with the launch of Webpacker.) However, migrating React components to support Webpack required changes to every single file adding ES6-style imports, file moves/renames, and scoping changes. This would have been too large to do all at once. What if there was a way to slowly migrate the JS code from Sprockets to Webpack, making components in either side available to the other side?

Continue reading “Migrate Sprockets to Webpacker with React-rails”

Fast development environments

Setting up new hosts entries for every different web site that you develop is hard. This workflow allows you to completely automate it. First thing you’ll want to do is setup a wildcard DNS record that points to your host. This allows you to dynamically setup new development websites without having create new DNS records for each one of them. I created a fake internal-only TLD on my local network’s DNS server that automatically returns the IP address of my development VM for any query to *.devvm. If you don’t have access to that, you could re-use an actual domain and automatically forward something like *.dev.technowizardry.net to the VM. For example, I have the ASUS RT-AC68U router for my personal network. So I SSH’d to the router, typed vi /etc/dnsmasq.conf, then appended:

Continue reading “Fast development environments”