These days I keep looking for new languages that compile to JavaScript. After spending years programming that language and starting from pure hate, going through hot love, and arriving at mild dislike, I still think the web deserves something better. Why? Because, WAT (and thousand other reasons), that's why! Since browser and platform vendors don't seem to love the idea of peacefully retiring JavaScript to some appropriate nursing home, our only option is to be smarter and come up with languages that compile to JavaScript while protecting us from its sharp edges.

The latest entry in the web language space is Microsoft's TypeScript. It is very interesting because it pulls the Bjarne Stroustrup trick in JavaScript-land. Much like what C++ did to C back then, TypeScript defines a superset of the JavaScript language so that every JavaScript program can be compiled as valid TypeScript. That seems incredibly useful as the problem with all new languages is porting and interfacing with older code written in JavaScript. TypeScript seems to solve that problem quite nicely. At least in theory it does -- more about that below.

Taking TypeScript for a Ride around Node.js

To get my feet wet with TypeScript I decided to try it with a small Express web app running in Node.js. I didn't want anything huge or overly complex, yet I wanted a real app that did something useful like serve web pages. That is why I picked a bare-bones Express app that you can generate following the Getting Started guide or running the express executable to start a project from a predefined template. I went over the few JavaScript code files and "ported" them to TypeScript. It went pretty easy and involved just three steps:

  • Renaming .js to .ts.
  • Changing module imports to the TypeScript syntax
  • Referencing the external module definitions needed for my app: node.d.ts and express.d.ts.

The full sample project is available on GitHub.

Installing TypeScript on Linux

Nothing bumpy here. TypeScript is shipped as a Node.js module and installing it is a matter of running:

sudo npm install -g typescript

Note that I install it as a global module, so that I can have the tsc executable available to all users on my machine.

Compiling TypeScript Code

You just invoke the TypeScript compiler and pass it the "main" source file. In my case that is app.ts. Note that we are running on Node.js, so we have to tell the compiler it's working with Node modules by passing the appropriate switch:

tsc --module Node app.ts

TypeScript will discover imported modules and referenced language definitions and traverse all needed files. The end result is an app.js file that you run as any Node.js application:

node app.js

Turning JavaScript into TypeScript

The most important change you need to do is take care of package imports. In Node.js JavaScript you import packages with a statement like this:

var express = require('express')

The require function loads the module and returns an object that contains the exported functions and objects. You access those through the locally-bound variable.

As I said before, TypeScript makes modules a part of the language. You import modules with the import statement:

import express = module("express")

Let's compile that now:

~/w/javascript/express-typescript tsc --module Node app.ts
/home/hristo/w/javascript/express-typescript/app.ts(5,24): The name '"express"' does not exist in the current scope

Oops, we got our first compile-time error! And that is a tricky one too -- I am pretty sure the "express" module is installed in my local node_modules folder and, to prove that, I can happily access it from a JavaScript file. Why doesn't TypeScript find it then? I googled for a bit and found out that imported modules have to have interfaces defined so that they can be imported. There are two ways to define module interfaces:

  • You have a module written in TypeScript that exports some objects. No problem here -- the compiler knows how to handle those.
  • You augment your module with an interface definition file that the TypeScript compiler uses to infer what object the module exports at compile time. One such definition is the node.d.ts file that contains the interfaces for all built-in Node.js objects.

The latter option above seems like a real deal breaker. Either I don't understand something fundamental about TypeScript or its promise of seamlessly integrating JavaScript code is in fact... a lie. For example, go take a look at the ImageBoard sample application. It contains an express.d.ts definition file for Express that seems manually built. And before you tell me to just use that, have in mind that it targets Express 2.x and I'm using the latest Express 3.0 release.

To trick TypeScript into accepting my Express module code, I had to define my own express.d.ts file and slap a bunch of vague definitions. Here it is:

declare module "express" {
  export function createServer(): any;
  export function favicon(): any;
  export function bodyParser(): any;
  export function methodOverride(): any;
  export function errorHandler(): any;
  export function logger(name: string): any;
  export function static(path: string): any;
}

Note how I declare a bunch of exported functions that return any objects. That is TypeScript's way of saying "I have no idea about that object's type, methods, or properties." The last step we need is to refer to that definition file (Warning! Ugly XML in JavaScript comments ahead.):

///<reference path='node/node.d.ts' />
///<reference path='node/express.d.ts' />

Having that in place, our app compiles and runs fine. Mission accomplished. Yet, I'm left with a bitter taste in my mouth. I really wish that the team adds a way to import "foreign" modules without forcing you to add boilerplate definition files.

Browser screenshot of our app

The Vim Experience

I was writing my code in Vim and wasn't anything like the Visual Studio experience. I am not too addicted to smart code completions and I can happily live without them, but I can imagine some people freaking out without them. I share Miguel de Icaza's sentiments that TypeScript needs better non-Microsoft tools. Hopefully people will fill that gap soon. The Vim development story, could easily get a lot better by basing a TypeScript syntax highlighter on one of the JavaScript ones. Doing the same for ctags will get us to an almost IDE-like experience.

Summary

TypeScript is really powerful, and I like it so far. It is perfectly usable outside the Microsoft ecosystem, but it requires some extra effort.