November 13th, 2007 by Stefan Rusek

Wasabi: Client-side Code

Because the first versions of Wasabi had to be mostly compatible with ASP, Wasabi retains some (though thankfully not all) of the problems that exist in PHP or ASP. Yet one of the beauties of having our own in-house compiler is that we can fix those problems, piece-by-piece, or, at any point, write a one-shot compiler back-end for the language of our choosing. (Another really cool option is to have Wasabi v2 generate Wasabi v3 syntax, so we could dramatically alter the syntax, if we wanted.)

For the moment, rather than using Wasabi as a one-shot way to move away from VBScript, we’ve instead opted to improve the compiler by adding features to make writing code more pleasant. We can’t make every change we’d like to make, since certain constructs cannot be translated easily into ASP or PHP, but we can still make some surprisingly large modifications. We added a type inferencer, which has greatly reduced the amount of wasted development time. But one of the features that has given us the most extra power has been adding client-side code generation.

Last week I talked about how we came up with picture functions so that we could solve an HTML generation problem. Picture functions were created to serve another purpose as well: they are a core part of what makes the ability to generate client-side code useful.

In FogBugz 5, the case view page took a long than we would have liked to render and send to the client, even though it didn’t really look as if it should. The reason was that we sent down a table with sections for viewing, editing, resolving, and assigning cases. If the case was an email it had replying and forwarding sections. This meant that we sent down a lot of data that we didn’t actually use most of the time, but if you clicked the edit button, the UI changed instantly without having to go back to the server. For FogBugz 6, we wanted to keep the quick response time for the case editing page, but cut down on the data that we had to send down.

One solution would be to write the UI on the server (for users who don’t have JS) and rewrite the UI in JS (for quick response times). But then we would have had two implementations of the same UI, and we would have had to fix bugs in two places and maintenance would be awful. Needless to say, that is not a path I encourage anyone to go down.

Back-ends for Wasabi are pretty easy to write, and you can add a new target language in a relatively short period of time, though it would admittedly take a bit longer for the needed runtime to be implemented in the target language. (In fact, I wrote an experimental Python generator in about five hours not long ago.)

Since we had Wasabi, we chose to add a JS back-end and the ability for the compiler to distinguish between code intended for the server, code intended for the client, and code intended for both. Once that was in place, the compiler just needed the Wasabi runtime library implemented in JS.

If a function or class is marked ExecuteAnywhere, then it will be generated in both the server language (ASP or PHP) and in the client language (JS). ExecuteOnClient means that the function or class should be generated on the client. The default is to generate only in the server language. Since client and server code use separate namespaces, two functions could have the same name and signature, but one be generated for the server and the other for the client. Here is an example:

<Picture, ExecuteAnywhere> Sub GenerateTable
    %><table><%
    Dim rs = GetTableData
    Do Until rs.EOF
        %><tr><td><%=rs("sName")%></td></tr><%
        rs.MoveNext
    Loop
    %></table><%
End Sub

Function GetTableData
    Dim cmd = GetCmd("SELECT * FROM Person WHERE fDeleted = 0")
    GetTableData = cmd.Execute
End Function

<ExecuteOnClient> Function GetTableData
    GetTableData = New RecordSet(DB.Person.Select( _
        Lambda(p) Not p.fDeleted))
End Function

So in the code above, the GenerateTable() function gets generated for both the server and the client, and when we need to actually get data, we have a server-side version that grabs the data directly from the database and a client-side version that grabs it from a local cache. We don’t completely avoid having two pieces of code that do the same thing, but we greatly reduce duplication, and the two versions of the same code can be right next to each other so that a change to one doesn’t involve hunting down the other version (that you may not even know exists).

To generate the table, we can call GenerateTable() directly, the table gets sent to the HTTP response, and the client can simply take a picture of it. Here is what might be generated for the client:

var gHtml = "";
function pictureOf(fxn) {
   var oldHtml = gHtml;
   gHtml = "";
   fxn();
   var result = gHtml;
   gHtml = oldHtml;
   return result;
}

function GenerateTable() {
   gHtml += "<table>";
   var rs = GetTableData();
   while (!rs.EOF) {
     gHtml += "<tr><td>" + rs["sName"] + "</td></tr>";
     rs.MoveNext();
   }
   gHtml += "</table>";
}

function GetTableData() {
   return new RecordSet(DB.Person.Select(
     function(p) { return !p.fDeleted; }));
}

Then to put the generated HTML to use we have another ExecuteOnClient function and an onclick event:

<a href="default.asp?generateTableOnServer"
     onclick="ShowTable(); return false;">Show Table</a>

<ExecuteOnClient> Sub ShowTable
   Dim oPlaceHolder = document.getElementById("idTablePlaceholder")
   oPlaceHolder.innerHTML = PictureOf GenerateTable
End Sub

Given the above code, the table appears almost instantly for users with JS enabled and still offers a reasonable fallback for non-JS browsers (such as most mobile phones).

So in FogBugz 6, if you click on a case, you get the HTML to view the case and a small JS object that can be used to generate all the other case editing pages. Client-side code generation is extremely easy to use and greatly reduces the amount of extra code that has to be written for web development.