Some thoughts and suggestions.

Topics: General
Oct 12, 2012 at 1:20 PM
Edited Oct 12, 2012 at 1:40 PM

Hello, I'm very impressed of your work, and I'm happy that MS get involved into js world. 

For the last years I worked on huge project made in GWT. The main problems with GWT was:

  • the rigid approach to crating the web app - In GWT you always have to work on app, you cant crate simple code snippet, compile it, use on the page and simple integrate with other snippets, this can be done only in "native" js. This is particularly inconvenient in pluggable architecture. 
  • compilation time - The compilation time for our project at the end take 15 minutes !!! Of course for development time we used dev hosted mode for debugging, but it works slowly and not all web mode bugs are appear in the hosted mode
  • error handling after compilation - There was many problems to find a bug in source code when bug appear on the production. 
  • ... and others  

After project ends I started looking for alternatives, like CofeeScript, ClousureCompiler, Hexe, and others. But I do not found the approach to solve all my problems. I want to create applications in static typed language, with simple debugging, make this quickly (without "edit-> compileeeeeeeeee -> run" cycle). I want to create pluggable architecture with simple code parts integration, especially integration with existing js libraries. So I started thinking about my own solution for all this problems.   

Main assumptions that I took:

  • there is no whole-source-code-compilation needed for running the application
  • code have to work in any browser without any plugin
  • simple, natural integration with js code
  • simple debugging in existing tools

The idea which was born, is to replace the whole-source-code-compilation by the single file (syntax, not contextual) translation on the fly, without any interaction from developer.

In the simple words: 

  • developer open the js file, 
  • file is translated on the fly by the editor (IDE) to high level static typed language, 
  • developer is writing code in the high level static typed language, 
  • the tool (IDE) on save translate edited file to javascript, 
  • developer click f5 on browse and everything works (if there is no bug :)).  
  • developer can also run the compiler to find compile time errors, optimize, obfuscate, .... create production ready code, and create "externs"/"declaration" file. 

This approach resolve the "edit-> compileeeeeeeeee -> run" cycle problem

For simple debugging, the translation have to preserve lines (source code have to be in the same line after translation).

Similar to TypeScript I choose the ES4/AS3 syntax as base language. For AS3 there are existing editors, on one hand AS3 is very similar to the JS, on second is static typed and easy to read/learn for Java/C# developers.  

I do not solve dependency resolving problem (super class have to be loaded before inheritance) , but developer can manage dependencies by self.

This is simple example how this can be done:

 

///////////////////////////////////////////////////////
// Base Class

// file org/Base.as
package org {
    public class Base {
        public static function base():String {
            return "Base.base()";
        }
    }
}

// Translated org/Base.as to js
var org = org || {}; (function() {
    /*/public class/*/ org.Base = function() {_EX_ || !this.Base || this.Base.apply(this,arguments); }; var Base = org.Base; {
        /*/public static/*/ Base.base = function ()/*/:String/*/ {
            return "base";
        };
    }
})();

///////////////////////////////////////////////////////
// IFoo Interface

// file org/test/IFoo.as
package org.test {
    internal interface IFoo {
        function foo():String
    }
}

// Translated org/test/IFoo.as to js
var org = org || {}; org.test = org.test || {}; (function() {
    /*/internal interface/*/ org.test.IFoo = function() {}; var IFoo = org.test.IFoo; {
        IFoo.prototype.foo = function ()/*/:String/*/ {};
    }
})();

///////////////////////////////////////////////////////
// Foo Class

// file org/test/Foo.as
package org.test {

    import org.Base;
    import org.test.IFoo;

    public class Foo implements IFoo {

        public function Foo() {
            console.log("Foo.Foo()");
        }

        public function foo():String {
            return "Foo.foo()";
        }

        public function baseFromFoo():String {
            return Base.base();
        }
    }
}

// Translated org/test/Foo.as to js
var org = org || {}; org.test = org.test || {}; (function() {

    var Base;
    var IFoo = org.test.IFoo;

    /*/public class/*/ org.test.Foo = function() { _EX_ || !this.Foo || this.Foo.apply(this,arguments); Base = org.Base; }; var Foo = org.test.Foo; {

        /*/public/*/ Foo.prototype.Foo = function() {
            console.log("Foo.Foo()");
        };

        /*/public/*/ Foo.prototype.foo = function()/*/:String/*/ {
            return "Foo.foo()";
        };

        /*/public/*/ Foo.prototype.baseFromFoo = function()/*/:String/*/ {
            return Base.base();
        };
    }
})();

///////////////////////////////////////////////////////
// Bar Class

// file org/test/Bar.as
package org.test {

    import org.Base;
    import org.test.Foo;

    public class Bar extends Foo {

        public function Bar(){
            super();
            console.log("Bar.Bar()");
        }

        public function bar():String {
            return "Bar.bar()";
        }

        public function barByReference():String {
            return this.execute(this.bar);
        }
        
        public function fooFromBar():String {
            return super.foo();
        }        

        public function fooByReference():String {
            return this.execute(this.foo);
        }

        public function baseFromBar():String {
            return Base.base();
        }

        public function execute(fn:Function):String {
            return String(fn.call(this));
        }
    }
}

// Translated org/test/Bar.as to js
var org = org || {}; org.test = org.test || {}; (function() {

    var Base;
    var Foo = org.test.Foo;

    /*/public class/*/ org.test.Bar = function() {_EX_ || !this.Bar || this.Bar.apply(this, arguments); Base = org.Base;}; var Bar = org.test.Bar; var _super = Foo; _EX_ = true; Bar.prototype = new Foo(); _EX_ = false; {

        Bar.prototype.Bar = /*/public/*/ function() {
            _super.call(this);
            console.log("Bar.Bar()");
        };

        /*/public/*/ Bar.prototype.bar = function()/*/:String/*/ {
            return "Bar.bar()";
        };

        /*/public/*/ Bar.prototype.barByReference = function()/*/:String/*/ {
            return this.execute(this.bar);
        };

        /*/public/*/ Bar.prototype.fooFromBar = function ()/*/:String/*/ {
            return _super.prototype.foo.call(this);
        };

        /*/public/*/ Bar.prototype.fooByReference = function()/*/:String/*/ {
            return this.execute(this.foo);
        };

        /*/public/*/ Foo.prototype.baseFromBar = function()/*/:String/*/ {
            return Base.base();
        };

        /*/public/*/ Bar.prototype.execute = function (fn/*/:Function/*/)/*/:String/*/ {
            return /*/String()/*/fn.call(this);
        };
    }
})();

 

Test for translated code.

 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
    "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <title></title>
        <script type="text/javascript"  src="org/Base.js"></script>
        <script type="text/javascript"  src="org/test/IFoo.js"></script>
        <script type="text/javascript"  src="org/test/Foo.js"></script>
        <script type="text/javascript"  src="org/test/Bar.js"></script>

        <script type="text/javascript">
            (function() {
                var base = new org.Base();
                var foo = new org.test.Foo();
                var bar = new org.test.Bar();

                base instanceof org.Base || console.log("Assert Exception");

                foo instanceof org.test.Foo || console.log("Assert Exception");
                !(foo instanceof org.Base) || console.log("Assert Exception");
                bar instanceof org.test.Bar || console.log("Assert Exception");
                bar instanceof org.test.Foo || console.log("Assert Exception");
                !(bar instanceof org.Base) || console.log("Assert Exception");

                !(foo instanceof org.test.Bar) || console.log("Assert Exception");

                org.Base.base() == foo.baseFromFoo() || console.log("Assert Exception");
                org.Base.base() == bar.baseFromFoo() || console.log("Assert Exception");
                org.Base.base() == bar.baseFromBar() || console.log("Assert Exception");


                foo.foo() == bar.foo() || console.log("Assert Exception");
                foo.foo() == bar.fooByReference() || console.log("Assert Exception");
                foo.foo() == bar.fooFromBar() || console.log("Assert Exception");

                bar.bar() == bar.barByReference() || console.log("Assert Exception");
            })();
        </script>
    </head>
    <body>

    </body>
</html>

 

The source code is 100% syntax correct as3. The translated version is 100% clean javascript. If you will open the AS3 code in editor like IntelliJ Idea all futures are working (intelisense , refactoring, ect.). 

This solution have the same limitations as TypeScript:
- manual dependency management 
- instanceof not work for interfaces
- etc.

But the key point is that compilation all of your code is not needed to see results of changes.
My idea is to move compilation to the later time to create the production ready, obfuscated code, which can be reused with auto generated "externs"/"declarations" file (to be compatible with as3 all public members have to be in "declarations", as3 have "internal" modifier). 

I do not have time to realize this idea. So maybe my thoughts will be useful for you guys.  

Regards

Pawel

Coordinator
Oct 12, 2012 at 3:18 PM

Thanks for sharing your idea with the TypeScript community.