Pequeña introducción

El día 1, Microsoft anunció TypeScript, un nuevo lenguaje de programación similar a Dart o a CoffeScript o a HaXe. Me enteré de casualidad (últimamente no sigo mucho Twitter) y en un principio no me llamó la atención. Pensé: otro Dart. Y lo cierto que Dart tenía buena pinta, pero no he acabado usándolo por ciertos motivos que ya comentaré en otro post. Pero luego me informé un poco más y me dejó completamente encandilado. Así que llevo 2-3 días trasteando a ratos con TypeScript para poder escribir una entrada introductoria que sea útil a la gente que quiera saber qué es y cómo usarlo.

¿Qué es TypeScript?

TypeScript es un lenguaje de programación tipado que está pensado para transformar su código a JavaScript. Es un superset de JavaScript (lo que quiere decir que cualquier JavaScript es también TypeScript) y tiene como objetivo ser una transición hacia las nuevas especificaciones de Ecmascript. Soporta tipado estático, clases, interfaces, múltiples definiciones de funciones y métodos, delegados “reales” (manteniendo el this sin tener que guardarlo y usar otro). Pero lo mejor de todo: hay creada una serie de herramientas de desarrollo de una calidad asombrosa (que es lo que Dart estaba empezando a  conseguir).

El compilador de TypeScript está hecho en el propio TypeScript y tiene herramientas de autocompletado y detección de errores también hechas en TypeScript. Eso quiere decir que ya hay un editor online con el que se puede crear TypeScript (el PlayGround de TypeScript). Esto es cojonudo porque próximamente imagino que lo veremos integrado en el editor de Cloud9.

Además de eso hay un plugin (gratuito) con herramientas de desarrollo para Visual Studio Express 2012 for Web (gratuito), que es genial.

El lenguaje en sí es un lenguaje abierto y el compilador es opensource. El código fuente se puede encontrar aquí:
http://typescript.codeplex.com/SourceControl/BrowseLatest

Ventajas de TypeScript

  • ¡Es completamente compatible con JavaScript! Eso quiere decir que puedes renombrar cualquier archivo .js a .ts e ir añadiendo tipos progresivamente. (Ni Dart, ni CoffeScript, ni HaXe son compatibles con Javascript).
  • Añade clases, interfaces, módulos, multiples prototipos de función, closures con this real…
  • ¡Se puede usar hoy mismo!
  • Hecho por el creador de C#, así que es bueno ya de base
  • Funciona tanto en cliente en cualquier navegador como en el servidor con Node.JS
  • Autocompletado y detección de errores desde ya

Desventajas de TypeScript

  • Requiere un proceso de precompilación antes de poder usarse (nada que no se pueda automatizar)
  • Al convertirse en JavaScript y no disponer de tipos, sigue con las mismas limitaciones en lo que respecta al tema de rendimiento (cosa que sí intenta mejorar Dart)
  • En el tema de código asíncrono, sigue con las mismas pegas que JavaScript. No tiene la posibilidad de crear código asíncrono secuencial. Dart, cuando se ejecute en su misma máquina virtual, puede que acabe teniéndolo. También es viable convertir código asíncrono secuencial en código JavaScript, pero dado que JavaScript no tiene el (tan odiado por algunos, no por mí) goto, es muy difícil de implementar y el código sin el goto quedaría como una máquina de estados muy guarra con un while y un megaswitch y muy difícil de comprender y de seguir.

Definiciones de módulos de Node.Js para TypeScript

La única “pega” que se le podría encontrar a TypeScript es que necesita archivos con definiciones más estrictos para poder disponer de autocompletado. Aunque no es necesario para usar librerías JavaScript, si tenemos las definiciones dispondremos de autocompletado y detección de errores de tipo en tiempo de edición y de compilación que de otra forma sería prácticamente imposible dada la extremada flexibilidad de JavaScript. Es por ello que decidí crear un proyecto donde ir poniendo definiciones de proyectos importantes de Node.JS.

El proyecto se puede encontrar aquí y acepto Pull Requests con nuevas definiciones o mejoras en las definiciones actuales: https://github.com/soywiz/typescript-node-definitions

Empezando con TypeScript

El compilador de TypeScript se puede instalar como un módulo de Node.JS. Así que lo único que tenemos que hacer es instalar Node.JS para nuestro sistema operativo. También podemos hacer pequeñas pruebas de un único archivo para cliente directamente desde el navegador con el PlayGround de TypeScript (como ya he comentado arriba). En cualquier caso, yo voy a centrarme en Node.JS.

Instalar el compilador de TypeScript

El compilador de TypeScript se puede instalar y ejecutar con Node.JS, simplemente poniendo:

Instalar typescript de forma global

npm -g install typescript

Compilar un archivo .ts en su equivalente .js

tsc file.ts

Aunque nuestro proyecto de servidor esté dividido en un montón de archivos .ts, el compilador de TypeScript detectará dependencias de módulos y compilará todos los .ts que sea necesario. De esta forma, actualizar el proyecto generalmente se hará con un único comando en el que compilaremos el entry point de Node.

Editores de TypeScript

Además de la detección de errores en tiempo de compilación, la gracia de TS es que dispondremos de detección de errores y de autocompletado directamente desde nuestro editor. Por ahora la única versión que funcione en todos los sistemas operativos principales es el PlayGround, pero estoy convencido de que estando en JavaScript el editor de PlayGround online, no tardaremos en verlo incluido en Cloud9 o de que se crearán editores multiplataforma. No debería ser especialmente difícil crear un wrapper en Java que plante un navegador que ejecute ese JavaScript y que además permita abrir varios archivos y/o gestionar proyectos. O quizá también un módulo para eclipse que use algún motor de JavaScript para el autocompletado y la detección de errores en tiempo de edición.

Pero como todo eso es todavía hipotético y yo trabajo actualmente en Windows, disponemos de un plugin para Visual Studio 2012 Express for Web que aunque todavía no es perfecto, sí que es una maravilla. La edición de archivos y el autocompletado es cojonudo. Lo único que veo que falla es la gestión de proyectos. Pero nada que no podamos solventar.

  1. Descargar Visual Studio 2012 Express for Web: http://www.microsoft.com/web/gallery/install.aspx?appid=VWD11AzurePack
  2. Descargar Plugin: http://go.microsoft.com/fwlink/?LinkID=266563

Nótese que el plugin únicamente es compatible con la versión “for Web” del Express. No funciona con las versiones Express de “for Windows 8” o “for Desktop”. Aunque sí con la versión Ultimate.

Características del lenguaje resumidas por encima

Referencias:

Para hacer referencia a archivos .ts directamente en el ámbito actual/global del archivo e incluir de esta forma archivos de definición, se hace mediante un comentario de /// con un tag xml reference, de la siguiente forma:

/// <reference path="relative_path_to.d.ts" />

Especificación de tipos y opcionalidad:

En cualquier punto donde se especificasen declaraciones de nombres tanto en el ámbito de variables locales, como en el de parámetros de función, se pueden especificar tipos de la misma forma que se haría en ActionScript o HaXe. En el caso de las funciones se puede especificar que un argumento es opcional, añadiendo el caracter “?” al final del nombre:

var text: String = "text";  
function func(arg1: any, arg2: number, callback?: (result: number) => void) { }  
function createVector(x: number, y: number): { x: number, y: number };

En el caso de variables locales, o lambdas, aunque puede ser útil especificar tipos para restringir su uso, el tipo se propagará e inducirá según las asignaciones que hagamos a la variable de forma que para escribir menos y que quede más limpio, podemos omitirlas. Lo mismo pasa con el tipo de retorno de una función, si no lo especificamos, se hará un análisis/propagación de tipos y se detectará el valor de retorno en función de los tipos de las expresiones return (cogiendo el tipo común más general, siendo el peor de los casos any)

Tipos básicos:

  • bool – true o false
  • number – enteros o valores de coma flotante
  • null
  • undefined
  • String (ahora string)
  • any – cualquier valor válido en javascript

Tipos comunes:

  • Function
  • RegExp

Tipos compuestos integrados:

  • ARRAYS: Object[]
  • DELEGADOS: (parameters, …) => return_value
  • INTERFACES INPLACE: { … }

Declaraciones ambientales:

Permiten declarar variables, funciones, o clases que estarán disponibles en el código aunque sin emitir código javascript al respecto. Esto es útil en los archivos de definición especialmente. Y se hace mediante la palabra reservada “declare”:

declare var document;  
declare var $;  
declare module module_name { }  
declare class Class { }  
declare interface Interface { }

Interfaces:

Las interfaces de TypeScript no son las interfaces a las que estamos acostumbrados, que tienen que implementar otras clases. En el caso de TypeScript una interfaz no tiene por qué ser implementada por una clase; simplemente define los campos que se espera que tenga un objeto:

interface MyInterface {  
    text: String;  
    num: number;  
    callback?: (a: bool, b: any) => MyInterface;  
};

Así pues. Esto sería válido (y el propio compilador/editor detectará un error y sabe con certeza que a la función test no se le va a pasar un objeto que cumpla esa interfaz):

function test(value: MyInterface) { }  
test({ text : hello, num : 10 });

Interfaces “callables”:

Como javascript es un lenguaje de prototipos, los objetos, pueden ser funciones llamables que contengan campos, así que las interfaces permiten reflejar ese comportamiento. La forma de definir cómo se llama a una interfaz, es creando un campo de tipo función, sin nombre. De la siguiente manera:

inteface MyFunction {  
    (parameters, ) : MyFunction;  
}

Clases:

En JavaScript las clases se definen como funciones que tienen su propio ámbito y que tienen un prototipo al que se accede si no se encuentra un campo en el ámbito definido. Esto permite simular muchos comportamientos incluyendo clases. En TypeScript, además de soportar la programación prototipada (por ser un superset de JavaScript), disponemos de clases. Las clases, aquí son bastante parecidas a otros lenguajes de programación, incluyendo el soporte de herencia simple:

class Point {
    x: number; // Field
    y: number; // Field

    constructor(x: number, y: number) { // Constructor
        this.x = x;
        this.y = y;
    }
    add(that: Point) { return new Point(this.x + that.x, this.y + that.y) } // Function
    sub(that: Point) { return new Point(this.x - that.x, this.y - that.y) } // Function
    get dist() { return Math.sqrt(this.x * this.x + this.y * this.y); } // Property
    static zero: Point = new Point(0, 0); // Static field
    static distance(p1: Point, p2: Point) { return p1.sub(p2).dist; }
}

class Point3D extends Point {
    z: number;

    constructor(x: number, y: number, z: number) {
        super(x, y);
        this.z = z;
    }
}

TypeScript soporta shortcuts para los constructores que lo que hacen es asignar parámetros a sus campos. De forma que lo de arriba se puede reescribir como:

class Point {
    constructor(public x: number, public y: number) {
    }
}

class Point3D extends Point {
    constructor(public x: number, public y: number, public z: number) {
    }
}

Cualquier función de TypeScript permite definir varias declaraciones para un mismo método (con el mismo nombre, pero con distintos argumentos):

function abs(x: number): number { return Math.abs(x); }  
function abs(v: Point): number { return new Point(abs(v.x), abs(v.y)); }

Módulos:

Los módulos pueden ser archivos completos o haber varios módulos declarativos en un mismo archivo. El concepto de módulo en JavaScript es de un ámbito encapsulado que define una serie de variables, funciones, clases, etc. y que está aislado del resto y que evita causar cambios inintencionados en el ámbito global. Además los módulos únicamente exportan ciertos elementos a los que se les presente dar una visibilidad desde fuera.

Para definir un módulo de declaración:

declare module "module_name" {  
}

Par importar un módulo que esté incluído en las referencias:

import module_name = module(module_name);

Para importar el namespace de un módulo/archivo completo:

import module_name = module(./path/to_file_without_the_ts_extension);

En node.js, se reemplazará la estructura del lenguaje module, por su equivalente require. Se puede seguir utilizando el require, pero si tenemos la declaración del módulo y usamos module, tendremos información de tipos.

Para definir un módulo con código (no de declaración) de TypeScript:

module Module {  
    var private_variable;  
    function private_function() { }  

    export var public_variable;  
    export function public_function() {}  
}

Funciones anónimas clásicas y closures con acceso real a this

Además de las funciones/callbacks/delegados anónimos clásicos de JavaScript, TypeScript soporta un nuevo tipo de delegados que conservan el this original de la clase, en vez de usar el this que se le pase a la función.

class Counter {  
    private var counter: number;  
    constructor() {  
        this.counter = 0;  
    }  
    function getIncrementor() {  
        return () => {  
            return this.counter++;  
        };  
    }  
}  
var counter = new Counter();  
var incrementor = counter.getIncrementor();  
incrementor();  
incrementor();

Ejemplo funcional (nodejs 8+express3+swig+mongodb):

Para la gente con poco tiempo que quiera “tocar” más que leer, he creado un paquete con un mini proyecto cutre de ejemplo donde se ve cómo usar TypeScript y cómo usar el proyecto de definición de módulos de github que creé ayer: