Harmony with TypeScript
On the way to harmony
Not so long ago Microsoft announced a new language, targeting on front-end developers. Everybody’s first reaction was like “Why on Earth?!” Is it just Microsoft darting back to Google? Is that a trick to promote their Visual Studio?
But anyway, what’s wrong with JavaScript? Why everybody is so keen to replace it? JavaScript for a language designed in only 2 weeks is freakishly powerful and flexible. It was released in December 1995 and got standardized as ECMA-262 in June 1997. Since that it has been evolving and currently ECMAScript 5 (JavaScript 1.8) implemented in all major browsers. But JavaScript still has its bad parts.
The ECMA committee decided to keep to backward compatibility and just introduced a directive “use strict” to switch the interpreter to the mode where the biggest evils of the JavaScript trigger errors. However it doesn’t help when JavaScript fails on guessing type. Yeah, dynamic typing is still considered as a benefit of JavaScript, but at the same time that is the cause of many problems. Let’s observe an example. We have a function, which makes decision based on value of the argument. Now we take the value of element.style.opacity and pass it to the function. Any assertion in the function that argument is 1 or 0 will fail. Why? Just because style properties even when they look like numbers, are strings. Thus we’ve got an error which not easy to spot. We could avoid it by checking types of the arguments on entry point of the function. This way we would bring pretty much of cross-cutting concerns (the functionality not related to the main logic) into the functions. Other languages with dynamic typing have such technique as type hinting. Why is there nothing like that in JavaScript?
Well, back in 1997 the ECMA committee started to work on 4th edition heavily based on Netscape JavaScript 2.0. ECMAScript 4 was supposed to have optional type annotation and type hinting, besides, with classes, modules, generators, iterators, destructors and algebraic data types. It didn’t go smoothly because the proposed changes were to make the language not really backward compatible to previous editions. The committee was debating for 18 months and at the end abandoned the edition. Though ECMAScript 4 proposal was superseded by a new project, code-named ECMAScript Harmony. Depending on ECMA, it may end up being called ECMAScript, 6th edition. As you see, ECMA is pretty well aware of JavaScript flaws, but it may take years until they come up with a new standard.
Some folk at Google decided that no improvement can fix JavaScript. The only way to have a decent client-side language is to create a new one from the scratch. So they presented their Drat language. Dart is a class-based, single inheritance language which supports interfaces, abstract classes, modules and optional typing. Doesn’t it sound familiar? But anyway, they seem to give a new better language to developers right now. Isn’t it a solution? Brendan Eich, creator of JavaScript and CTO at Mozilla says: “Dart is one of the many languages that currently compiles to JavaScript, and that’s a lot to say about that because like in Native Client from Google, I don’t think Dart is going to be natively supported ever in other browsers. Not in Safari, not in IE”. Well, whatever good Dart is, as of now no browser except Chrome has plans to embed Dart VM. Yes, Dart can be compiled to JavaScript, but the output code inevitably extended with Dart support library gets overhead and is not easy to debug. They have huge community and plenty of optimism, but it seems as they have failed already.
Now we’ve got TypeScript, another fine language for the front-development. Will this story be any different?
Ladies and gentlemen, here is TypeScript
So what is TypeScript? TypeScript is free and open source. So you can clone or fork the project on CodePlex. They didn’t propose any really new syntax like Drat or even CoffeScript, but borrowed ECMA proposals. TypeScript is first and foremost a superset of JavaScript as Nicholas Zakas calls it. It means that you write regular JavaScript within TypeScript and it stays valid. When done, you compile TypeScript to JavaScript. By default the output code will be ECMAScript 3 compliant, but you can have ECMAScript 5 by simply using corresponding compiler option. Thus, it’s not about replacing JavaScript, but rather augmenting with the features of ECMAScript Harmony proposals. Well, Brendan Eich believes the Harmony can make 6th edition even next year. Browser vendors are going to be pretty quick with implementation. It will come on the latest browser versions. So if you’re required to support any older ones, you won’t be able simply to switch to the new JavaScript syntax. On the contrary, with TypeScript you can start using Harmony right now and be sure the compiled JavaScript will work supposedly on any browser. Besides, you won’t have any redundant code after compilation. Type checking is performed by the compiler and doesn’t appear into output code. Classes, interfaces, arrow expressions and such are implemented by the patterns you are likely familiar with. However, enough for now. Let’s see what the language really is.
The language
Type annotation and hinting
As you already know, TypeScript provides type annotation and static type checking. Annotated type can be “any”, one of primitive types (number, string, bool, null, undefined), object and void type.
var inx: number = 1,
text: string = "Lorem",
isReady: bool = false,
arr: string[],
obj: ObjInterface = factory.getObj(),
mixedVal: any;
Object type consists of class, module, interface and literal types. The notation can have following members:
- Properties, which define expected object structure (property – value type pairs);
- Call signatures, which define parameter types and return type for applying a call operator (function invocation);
- Constructor signatures, which define parameter types and return type for applying a new operator (object instantiation).
var obj: { x: number, y: number },
fn: ( x: number, y?: any ) => number,
constr: new() => ObjInterface;
Let’s take a look at the following example. Here is a function expression, which takes an array and a function for arguments.
var treatItems = function( arr,
callback ) {
// do something
return arr;
};
How much of code it would take to have type checking on the entry point? Type checking for the argument function? Whatever good you are in JavaScript I bet it would be more than that:
var treatItems = function(
arr: string[],
callback: (value: string,
inx: number,
arr: string[]) => string[]) {
// do something
return arr;
};
If we compile this TypeScript code, we will get the following output:
var treatItems = function( arr,
callback ) {
// do something
return arr;
};
Have you spot any difference comparing to the initial JavaScript? …There’s no difference.
Classes
The class syntax implemented by TypeScript must look familiar to you. Just check it out:
class Mamal
{
private nickname: string;
constructor( nickname: string = "Noname" ) {
this.nickname = nickname;
}
public getNickname():string {
return this.nickname;
}
}
class Cat extends Mamal
{
private family: string = "Felidae";
constructor( nickname: string ) {
super( nickname );
}
public getFamily():string {
return this.family;
}
}
Now let’s see what will be the output JavaScript after compilation. As we have here one class extending another one, the compiler adds corresponding helper:
var __extends = this.__extends || function (d, b) {
function __() { this.constructor = d; }
__.prototype = b.prototype;
d.prototype = new __();
}
Nicely written, concise and expressive. And now the classes:
var Mamal = (function () { ... })();
var Cat = (function (_super) {
__extends(Cat, _super);
function Cat(nickname) {
_super.call(this, nickname);
this.family = "Felidae";
}
Cat.prototype.getFamily = function () {
return this.family;
};
return Cat;
})(Mamal);
As you see, the generated JavaScript looks like human-written. It won’t be all that difficult to debug.
Interfaces
In TypeScript as you declare an interface it gets available for type annotation and hinting.
interface Point {
x: number;
y: number;
}
function getDistance( pointA: Point, pointB: Point ) {
return Math.sqrt(
Math.pow( pointB.x - pointA.x, 2 ) +
Math.pow( pointB.y - pointA.y, 2 )
);
}
var result = getDistance(
{ x: - 2, y: - 3 }, { x: - 4, y: 4 })
There is nothing like class implements interfaces in TypeScript. I reckon that is because of dynamic nature of JavaScript.
One interface can inherit from another interface (base one):
interface Mover
{
move(): void;
}
interface Shaker
{
shake(): void;
}
interface MoverShaker extends Mover, Shaker
{
}
Modules
ECMAScript 6 proposal implies that module is a unit of source contained within a module declaration or within an externally-loaded file. We are used to local modules made using self-called anonymous function. In TypeScript we specify which module members are accessible from outside by export keyword.
module graphic
{
export class Point
{
x: number;
y: number;
constructor( x: number = 0, y: number = 0 )
{
};
}
}
var point = new graphic.Point( 10, 10 );
External modules are nothing new for those who use AMD-compatible libraries (e.g RequireJS). TypeScript doesn’t introduce any module loader, but relies on AMD or CommonJS (you can choose one in compiler options).
// File main.ts:
import log = module ( "log" );
log.message( "hello" );
// File log.js:
export function message(s: string) {
console.log(s);
}
Arrow expressions
TypeScript brings also some of syntatic sugar; as far as it stated in ECMAScript 6 proposals. Functions can be declared in the same manner as in CoffeScript or, if you wish, in Ruby.
(x) => { return Math.sin(x); }
(x) => Math.sin(x);
x => { return Math.sin(x); }
x => Math.sin(x);
But do we really need it?! It can be very useful when declaring callback expressions. The difference makes the fact that arrow expression unlike function one doesn’t have its own context and “this” refers to the calling function context. Thus, we can avoid that = this redundant statements eventually.
var messenger = {
message: "Hello World",
start: function() {
setTimeout( () =>
{ alert( this.message ); }, 3000 );
}
};
messenger.start();
Type assertions
In TypeScript for polymorphic function return we can explicitly asserts a type. That simple:
class Shape { ... }
class Circle extends Shape { ... }
function createShape( kind: string ): Shape {
if ( kind === "circle" ) return new Circle(); ...
}
var circle = <Circle> createShape( "circle" );
Ambient declarations
Ambient declarations provide type information for entities that exist “ambiently” and are included in a program by external means, for example by referencing a JavaScript library
Here an extract of type information for an external library:
interface JQuery
{
text(content: string);
}
interface JQueryStatic {
get(url: string, callback: (data: string) => any);
(query: string): JQuery;
}
declare var $: JQueryStatic;
Source files dependencies
Type information for a sophisticated library can take pretty much of code (type info for YUI3). It would be kind of disaster to keep it in the same file with application code. Luckily, TypeScript provides a special notation to refer data describing files. Here in the example all the jQuery type information is located in jquery.d.ts. The application code has the reference. Now if we violate any of jQuery interfaces, the compiler will trigger an error.
/// <reference path="jquery.d.ts" ></reference>
module Parallax
{
export class ParallaxContainer
{
private content: HTMLElement;
constructor( scrollableContent: HTMLElement ) {
$( scrollableContent ).scroll(( event: JQueryEventObject ) => {
this.onContainerScroll( event );
});
}
private onContainerScroll( e: JQueryEventObject ) : void {
// do something
}
}
Environment setup
The easiest way to install TypeScript compiler is to do it using NodeJS package manager:
npm install -g typescript
Now we can compile a TypeScript file:
tsc example.ts
Do you want ECMAScript 5- compliant output?
tsc --target ES5 example.ts
You can even have TypeScript compilation run-rime.
As we start to code TypeScript, our development flow changes. With JavaScript it was enough to refresh the site page to see the changes, now we need a building script which compiles all the TypeScript files. My favorite build processes automating software is Apache Ant. Usually I use Ant building script to compress and concatenate CSS and JavaScript files, to run unit-tests. On some projects it also preprocesses SASS. With Ant you can describe as XML what you need on building. It is easy to read and quite comprehensive to cover most of your requirements. You can find details in
Maintainable JavaScript by Nicholas C. Zakas. Here is just an example showing how to add TypeScript compilation task into your building script:
<?xml version="1.0"?>
<!DOCTYPE project>
<project name="tsc" basedir="." default="build">
<target name="build">
<!-- Compile all .ts files -->
<apply executable="tsc" parallel="true">
<srcfile></srcfile>
<fileset dir="." includes="**/*.ts"></fileset>
</apply>
<!-- Lint all required CSS, JS files -->
<!-- Concatenate all required CSS, JS files -->
<!-- Compress built CSS, JS files -->
</target>
</project>
Now we just run the script and we are fine:
ant
Conclusion
As Douglas Crockford said “Microsoft’s TypeScript may be the best of the many JavaScript front ends”. As for now that’s maybe the best way to start with ECMAScript Harmony. However, perspectives of TypeScript are not all that “serene”. At the moment we have an early experimental release of TypeScript, which is meant to give developers a preview of what’s to come. It doesn’t look that raw as Dart, but still may be risky to use on the projects. Besides, currently TypeScript based on ECMAScript 6 proposals. The specification may change dramatically before ECMAScript 6 gets finalized. What will happen then to TypeScript? Microsoft will have to decide either they stick to ECMAScript 6 or stay compatible to their current specification. That all brings too much of ambiguity on foreseeable future of TypeScript. But, nevertheless, TypeScript is definitely worth of keeping up with.