Porting Razor to TypeScript

Having not tackled any major TypeScript projects recently, the language itself has continued to be at the top of my 'to learn' list. I wanted to find a new project that I could use to learn TypeScript, and I decided on tackling a port of the Razor parsing framework to TypeScript.

I've enjoyed significant experience with the Razor parsing framework, initially through my open source project RazorEngine and most recently through my new project FuManchu, a C# implementation of HandlebarsJS using the Razor parsing framework and MVC's metadata framework.

Now, converting any library like this to JavaScript for the browser or NodeJS is quite an undertaking, but given how clean the codebase and any lack of significant dependencies, it's seems to me a perfect fit.

Tools

To tackle this project, I'm going out of my comfort zone - that means dropping Visual Studio in favour of a simpler text editor - and Visual Studio Code is again, a perfect choice. I still gives me intellisense, and provides built-in support for TypeScript. I also considered alternatives such as Sublime, Brackets or Atom, all using their respective OmniSharp plugins.

Razor itself comes with a wealth of unit tests already predefined, so that assists ensuring that my TypeScript port at least conforms to the same set of tests that the main project supports, but additionally we're not building a C# language parser, I'm attempting an ECMAScript 6 language parser, so although there are a great many similarities, there are also differences. My unit test framework of choice is Jasmine, combined with Karma. I hope to implement the majority of the unit tests as proof of the implementation.

Where to start

There is a lot going on in Razor, from the tokenizers, to language parsers, chunk generators and tag helpers, it's important to break down the project into smaller blocks which can be ported and tested. So let's start at the beginning, and tackle some simple concepts - reading text. Razor provides a number of text reader implementations that either operate and implement some abstract contract types - the ITextBuffer and ITextDocument. Most of the text readers implement from .NET's TextReader type also - which we'd need to implement. Then there are a few utility types, such as StringBuilder, IEquatable<T>, IComparable<T> and of course, IDisposable.

IDisposable is an interesting concept as this is very much implemented with a language feature in C# and Visual Basic, the using statement. We can implement something similar in TypeScript, and proide that as an export function as part of library.

Firstly, let's define IDisposable:

namespace Razor
{
  export interface IDisposable
  {
    dispose(): void;
  }
}

And now let's implement our using function.

namespace Razor
{
  export function Using(contextOrDisposable: any|IDisposable, disposableOrAction: IDisposable|Function, action?: (disposable: IDisposable) => void)
  {
    if (arguments.length === 2)
    {
      action = <(disposable: IDisposable|Function) => void>disposableOrAction;
      disposableOrAction = <IDisposable>contextOrDisposable;
      contextOrDisposable = null;
    }

    try 
    {
      action.apply(contextOrDisposable, [disposableOrAction]);
    }
    finally
    {
      (<IDisposable>disposableOrAction).dispose();
    }
  }
}

We're taking advantage of TypeScript's ability to support multiple types as arguments - this helps us implement a form of method overloading, but it means our method has to consider and/or test arguments to determine the actual intention of the method calls. This doesn't feel entirely right to me, and it's something I may come back and revisit and refactor. Now, one thing that TypeScript handles really well, is the control of closure scope - that being the meaning of this, so although I've provided arguments for specifying the context of your disposable closure, if you're taking advantage of TypeScript's support for arrow functions (=>), it handles this for you in the generated code.

import using = Razor.Using;

export class MyClass
{
  public get someProp(): string
  {
    return "value";
  }

  public someMethod(): string
  {
    var disposable: IDisposable = // ... some disposable instance
    using (disposable, () =>
    {
      // "this" still means the instance of "MyClass
      var value = this.someProp;
    });
  }
}

Next series post

The next post in the series will deal with implementing our text services - including the SourceLocation type and our text readers.

You can follow my progress at the GitHub rep - https://github.com/Antaris/RazorJS

comments powered by Disqus