: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.
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!
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);
}
}
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:
Add override
keyword on methods overriding a parent class method (when the parent class is in the project)
Add in the final directory a support file Ts2Hx.hx
required in some cases to make transpiled files work fine.
Replace some compiled files with original haxe files if needed (using --haxe
option), allowing to use alternative implementations of specific classes in Haxe.
When compiling a typescript file, ts2hx performs conversions to make the haxe code behave the same as its typescript counterpart.
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' };
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
.
integer
type in typescriptAdd 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;
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;
Typescript
enum EnumExample {
VALUE1,
VALUE2,
VALUE3,
VALUE4,
VALUE5
}
Haxe
enum EnumExample {
VALUE1;
VALUE2;
VALUE3;
VALUE4;
VALUE5;
}
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);
}
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 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;
}
}
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;
}
}
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';
}
}
}
console.log
becomes trace
Typescript
console.log('hello');
Haxe
trace('hello');
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);
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 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 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));
}
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.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);
You can find more examples in the examples/
directory, such as try
/catch
, do
/while
etc...