jeremyfa / node-ts2hx

Typescript to Haxe transpiler written in JavaScript
MIT License
101 stars 16 forks source link

ts2hx

:rotating_light: This library is not maintained anymore (it was a proof of concept) :rotating_light:

Compile/Transpile typescript code to ready-to-run haxe code.

Why?

Typescript is officially supported by several IDEs (Webstorm/IntelliJ, Visual Studio), making it quite convenient to use. Then came the idea of writing a Typescript to Haxe transpiler. Typescript got ECMAScript roots and static typing which are pretty similar to Haxe.

Using ts2hx, I am able to compile a pixi.js-based (http://pixijs.com) HTML5 app written in Typescript and make it work at almost native speed on mobile devices with openFL CPP target (yes, it becomes possible to compile Typescript to C++!). The only code that needs to be re-written in Haxe is the platform-specific code (use openFL API instead of pixi.js etc...). If all the platform-specific code is properly encapsuled in reusable classes, the rest of the code (all the app logic) can become 100% portable and compilable to valid haxe code.

That said, keep in mind this project is experimental. Still very fun to implement!

How to use

Install package

npm install ts2hx

Example

var ts2hx = require('ts2hx');

// Compile typescript code
var haxeCode = ts2hx([
    "class FooClass {",
    "    constructor(public name:string) {",
    "        console.log('Hello, my name is '+this.name);",
    "    }",
    "}"
].join("\n"));

// Log haxe output
process.stdout.write(haxeCode);

Expected output:

package;

class FooClass {

    public var name:String;

    public function new(name:String) {
        this.name = name;
        trace('Hello, my name is ' + this.name);
    }

}

Command Line Interface

You can install the cli command:

npm install -g ts2hx

Then run it:

ts2hx someFile.ts > result.hx

You can also build a full directory of typescript files (recommended):

ts2hx --typescript dir/to/typescript/files --destination dir/to/compiled/haxe/files

When building a full directory, ts2hx will be able to perform additional tasks:

Compilation rules

When compiling a typescript file, ts2hx performs conversions to make the haxe code behave the same as its typescript counterpart.

Core types

Typescript

var foo:number = 1;
var bar:string = "Hello";
var baz:boolean = true;
var qux:any = { some: 'values' };

Haxe

var foo:Float = 1;
var bar:String = "Hello";
var baz:Bool = true;
var qux:Dynamic = { some: 'values' };

Integers

The Int type doesn't exist in typescript. However, it is still possible to transpile number to Int when transpiling thanks to type inference or a custom typescript interface integer.

Add the integer type in typescript

Add a typescript definition file to your project (integer.d.ts)

interface integer extends number {}

You can then use integer in your typescript code while still manipulating numbers in compiled javascript:

Typescript

var foo:integer;
var bar:number;

Haxe

var foo:Int;
var bar:Float;

Integers by type inference

If you really don't want to use integer interface, you can still create haxe Int using type inference:

Typescript

var foo = 1;
var bar = 1.0;

Haxe

var foo:Int = 1;
var bar:Float = 1.0;

Enum

Typescript

enum EnumExample {
    VALUE1,
    VALUE2,
    VALUE3,
    VALUE4,
    VALUE5
}

Haxe

enum EnumExample {
    VALUE1;
    VALUE2;
    VALUE3;
    VALUE4;
    VALUE5;
}

Switchs

The break keyword in switch statements doesn't exist in haxe. Fall-through cases are converted to comma-separated cases.

Typescript

switch (value) {
    case 1:
    case 2:
        console.log('value is 1 or 2');
        break;
    case 3:
        console.log('value is 3');
        break;
    default:
        console.log('value is '+value);
}

Haxe

switch (value) {
    case 1, 2:
        trace('value is 1 or 2');
    case 3:
        trace('value is 3');
    default:
        trace('value is ' + value);
}

Interfaces

Typescript

interface MyInterface {
    x:number;
    y:number;
    foo():void;
}

Haxe

interface MyInterface {
    public var x:Float;
    public var y:Float;
    public function foo():Void;
}

Classes

Classes are properly converted, including typescript-specific features like getters/setters or properties in constructor signature.

Typescript

class FooClass extends BarClass implements QuxInterface, BazClass {

    private prop1:number;
    static prop2:number = 0;

    constructor(public prop3:number) {
        this.prop1 = 0;
    }

    get prop4():number {
        return this.prop1 + FooClass.prop2;
    }

    set prop4(value:number) {
        this.prop1 = value - FooClass.prop3;
    }

}

Haxe

class FooClass extends BarClass implements QuxInterface implements BazClass {

    private var prop1:Float;

    static public var prop2:Float = 0;

    public var prop3:Float;

    public function new(prop3:Float) {
        this.prop3 = prop3;
        this.prop1 = 0;
    }

    public var prop4(get, set):Float;

    public function get_prop4():Float {
        return this.prop1 + FooClass.prop2;
    }

    public function set_prop4(value:Float):Float {
        this.prop1 = value - FooClass.prop3;
        return value;
    }

}

Generics

Typescript generics are converted to haxe generics. The @:generic macro is added automatically in haxe code.

Typescript

class GenericClassExample<T> {

    constructor(content:T) {
    }

}

class GenericClassExample2<T extends InterfaceA> {

    constructor(private content:T) {
    }
}

Haxe

@:generic
class GenericClassExample<T> {

    public function new(content:T) {
    }

}

@:generic
class GenericClassExample2<T:InterfaceA> {

    private var content:T;

    public function new(content:T) {
        this.content = content;
    }

}

Closures

Typescript's double-arrow closures are converted to Haxe, ensuring this is still referencing the parent context.

Typescript

class Foo {

    public name:string = 'Foo';

    constructor() {

        var someClosure = () => {
            this.name += ' Bar';
        }

    }
}

Haxe

class Foo {

    public var name:String = 'Foo';

    public function new() {
        var __this = this;
        var someClosure = function() {
            __this.name += ' Bar';
        }
    }

}

Logs

console.log becomes trace

Typescript

console.log('hello');

Haxe

trace('hello');

setTimeout/setInterval

setTimeout and setInterval are converted to Ts2Hx.setTimeout and Ts2Hx.setInterval (requires Ts2Hx.hx support file).

Typescript

setTimeout(function() {}, 1000);
setInterval(function() {}, 1000);

Haxe

Ts2Hx.setTimeout(function() {}, 1000);
Ts2Hx.setInterval(function() {}, 1000);

Objects

Typescript objects are converted to haxe anonymous structures. The delete keyword and brackets access are converted to their closest equivalent in haxe (requires Ts2Hx.hx support file).

Typescript

var dict:any = {
    foo: 'bar',
    baz: 'qux'
};
dict['foo'] = 'baz';
dict.baz = 1234;
dict['foo' + dict.foo] = 'qux';
delete dict.baz;

Haxe

var dict:Dynamic = {
    foo: 'bar',
    baz: 'qux'
};
dict.foo = 'baz';
dict.baz = 1234;
Ts2Hx.setValue(dict, 'foo' + dict.foo, 'qux');
Reflect.deleteField(dict, 'baz');

For loops with incrementor

for loops with incrementor don't exist in haxe. They are converted to while loops.

Typescript

for (var i = 0, len = 12; i < len; i++) {
    console.log("for iteration #"+i);
}

Haxe

var i:Int = 0, len:Int = 12;
while (i < len) {
    trace("for iteration #" + i);
    i++;
}

For loops over object

for loops can be used to iterate over an object's keys.

Typescript

for (var key:string in someObject) {
    console.log('key: ' + key);
    console.log('value: ' + someObject[key]);
}

Haxe

for (key in Reflect.fields(someObject)) {
    trace('key: ' + key);
    trace('value: ' + Ts2Hx.getValue(someObject, key));
}

Call of forEach method of an Array

Array.forEach are transpiled to the closed equivalent in haxe (requires Ts2Hx.hx support file).

Typescript

['item1', 'item2'].forEach(function(value) {
    console.log(value);
});

Haxe

Ts2Hx.forEach(['item1', 'item2'], function(value) {
    trace(value);
});

JSON parsing and dumping

JSON.parse and JSON.stringify are transpiled to the closed equivalent in haxe (requires Ts2Hx.hx support file).

Typescript

var jsonValue:any = JSON.parse('{"a":1, "b": ["c", "d", "e"]}');
var jsonString:string = JSON.stringify(jsonValue);

Haxe

var jsonValue:Dynamic = Ts2Hx.JSONparse('{"a":1, "b": ["c", "d", "e"]}');
var jsonString:String = Ts2Hx.JSONstringify(jsonValue);

More examples

You can find more examples in the examples/ directory, such as try/catch, do/while etc...