Reakt: A Kotlin wrapper for React.js


#1

Greetings all,

I’ve just pushed a Kotlin wrapper for React up to github (https://github.com/andrewoma/reakt).

It’s a proof of concept at this point, but it does have a fully working TodoMVC example.

As it’s my first experience with using the JavaScript backend, I thought I’d share my thoughts (and hopefully get some help).

The Good

  1. It works! React doesn’t make inter-operating easy, but I’ve found a work-around in all cases. e.g. React substitutes the this pointer to different types, uses complicated factories and doesn’t understand properties defined in prototypes.
  2. Kotlin is expressive enough to build a typesafe HTML builder. e.g. https://github.com/andrewoma/reakt/blob/master/src/todo/components/TodoItem.kt
  3. Delegated properties saved the day - they allowed me to store attributes directly on an underlying JavaScript object for interoperation with React.
  4. The IDE is remarkably responsive - there’s been massive improvements here - great work!
  5. The JS backend seems almost complete (couldn’t use enums, nested classes within class objects, or suppress annotations inline)
  6. Source maps support is awesome


The Bad

  1. Compilation times blow out fairly quickly (14s at present). Admittedly, the project does model the entire DOM. Will incremental compilation ever be possible for JavaScript?
  2. The lack of documentation and/or examples on the JavaScript tool chain

    The Annoyances
    1. Having to stick suppress("UNUSED_PARAMETER") on native methods.
    2. Name mangling doesn't make inter-operation easy. e.g. why shouldComponentUpdate_wn2jw4$ and not shouldComponentUpdate when it's not overloaded?
    3. Source maps support seems a bit buggy. It's hit or miss as to whether the line numbers are correct.
    4. I had to mangle the source maps to replace file://... references to http://... for Chrome to read them for security reasons.
    5. Nullity on signatures seems patchy. e.g. document.getElementById() and String.match() are both incorrectly marked as NotNull.
    6. I had to fall back to native JavaScript at times and there's no way of doing it without creating a companion JavaScript file (nothing like GWT's inline JavaScript snippets).
    7. Kotlin isn't quite expressive enough to define an HTML builder without boilerplate - I ended up falling back to code generation.

    The Questions

    1. How does the standard library work with in with the JS backend? Creating a Kotlin (JavaScript) project in IntelliJ doesn't include the standard library. So far, I've got away without it, but can be included? (the maven build seems to automatically include it).
    2. How do I build and distribute a Kotlin Javascript library? Is it distributed as a source jar (GWT style)? It seems like there's a Kotlin source stub for the standard library. What generates it?
    3. How are tests run? I noticed there's a QUnit asserter? Can I make use of any of this or is it just for the standard lib? (I have got Karma tests running with a little mangling, but is there an endorsed way?)
    4. How do I tell IntelliJ that a module is Kotlin (JavaScript) after project creation? e.g. importing a maven project defaults to Kotlin (Java) and I can't work out how to force it to JavaScript.
    5. Not really JS related, but is there a maven repository somewhere that has the TeamCity builds in it? I constantly get cornered by updating my plugin to the latest nightly and not finding anything compatible for gradle/maven builds.

    Finally, you guys are doing a fanstastic job - I'm doing my best to sneak Kotlin into my office whenever I can. :-)

    Cheers,
    Andrew


    #2

    Andrew,

    Thanks for sharing! We will look deep into this, and will sure have a lot of questions about how it works and what was your experience in particular places.
    Are you using Kotlin M8 or one of the latest builds from TeamCity? I’m asking, because for M9 we are preparing a big update for JavaScript, including stdlib. For standard library, try recent builds from our Continuous Integration server. See instructions here.


    #3

    Hi Ilya,

    I’m using the IntelliJ 14 EAP builds with the nightlies (138.2458.8 and 0.8.1496 at present).

    For some reason the Kotlin plugin hasn’t updated to the latest nightly for a week or so (I’ll have a look into it).

    I’ve just checked build.txt in the project copy of kotlin-jslib.jar and that’s also 0.8.1496.

    I’ll try to get the absolute latest plugin build (I guess I need to manually extract the kotlin-jslib.jar/kotlin.js files and overwrite the ones IntelliJ set up on project creation).

    I’m happy to answer any questions you have (and would love some answers to mine ;)). I’m in Australia (GMT +11) so unfortunately it usually means I’m usually out of sync with most of the world!

    Update

    I’ve updated to 0.8.1615 and the standard library is included. Thanks Illya!

    (The problem was that the nightly repository url had changed from lastSuccessful to bootStrap and I hadn’t noticed).

    Kotlin.js is now a somewhat scary 327KB. Any chance of only including the functions of the standard library that are actually used? :wink:

    Cheers,
    Andrew


    #4

    The problem with nightly updates is likely to be caused by the changes in the CI server opertation. Try changing "http" tp "https" in the plugin repository URL in IntelliJ IDEA settings


    #5

    Hi, Andrew!

    Greate job!
    Thanks for sharing your project and your thoughts!

    I’m very sorry for the late answer!


    > doesn’t understand properties defined in prototypes.
    I think in your case you can workaround it by declaring things as native trait.

    > couldn’t use enums
    JS backend supports top level enums sine M6.1
    I replaced your LogLevel class by enum: http://kotlin-demo.jetbrains.com/?publicLink=113810345976161206390-137795029

    > suppress annotations inline
    Sinse M9 inline works for user declarated functions.
    And now we works to provide it for library functions.

    > The Bad
    > 1. Compilation times blow out fairly quickly (14s at present). Admittedly, the project does model the entire DOM. Will incremental compilation ever be possible for JavaScript?
    Of course incremental compilation is possible and someday we’ll do it :slight_smile:
    Anyway we’ll work to improve compilation time.

    > 2. The lack of documentation and/or examples on the JavaScript tool chain
    What kind of documentation are you missing?

    > The Annoyances
    > 1. Having to stick suppress(“UNUSED_PARAMETER”) on native methods.
    Related issue : KT-2141
    Please vote or star it to get updates.

    > 2. Name mangling doesn’t make inter-operation easy. e.g. why shouldComponentUpdate_wn2jw4$ and not shouldComponentUpdate when it’s not overloaded?
    We can’t do it, because we have to support separate compilation for modules and libraries.
    But we are thinking about simplifying it as much as possible.
    Some of possible solutions:

    • introduce an annotation for overload and prohibit overloads without explicit annotation in JS-backend
      ** maybe only in public API
    • introduce an annotation to disable mangling on particular members
      ** or just use platformName in JS to specify public name explicitly

    > 5. Nullity on signatures seems patchy. e.g. document.getElementById() and String.match() are both incorrectly marked as NotNull.
    Do you have a list of specific members that are incorrectly marked? We could fix at least most frequently used and revisit the whole issue later. Or you could just contribute fixes :slight_smile:

    > 6. I had to fall back to native JavaScript at times and there’s no way of doing it without creating a companion JavaScript file (nothing like GWT’s inline JavaScript snippets).
    We are working on fix :wink:
    Could you provide your usecases? When would you like to use it?

    > 7. Kotlin isn’t quite expressive enough to define an HTML builder without boilerplate - I ended up falling back to code generation.
    What do you mean by “boilerplate”, and what do you generate? Could you please elaborate on this?

    > The Questions
    > 1. How does the standard library work with in with the JS backend? Creating a Kotlin (JavaScript) project in IntelliJ doesn’t include the standard library. So far, I’ve got away without it, but can be included? (the maven build seems to automatically include it).
    Works since M9.

    > 2. How do I build and distribute a Kotlin Javascript library? Is it distributed as a source jar (GWT style)? It seems like there’s a Kotlin source stub for the standard library. What generates it?
    Right now we don’t have any way to distribute Kotlin JavaScript libraries.
    Related issues: KT-5712, KT-5711

    > 3. How are tests run? I noticed there’s a QUnit asserter? Can I make use of any of this or is it just for the standard lib?
    Internally we use QUnit asserter, but we don’t have generic way for tests in kotlin2js projects.
    I wouldn’t recommend using this internal tool, because it will definitely change. What do you think would be the best approach to testing in Kotlin compiled to JS?

    > (I have got Karma tests running with a little mangling, but is there an endorsed way?)
    Could you please share your experience with Karma?

    > 4. How do I tell IntelliJ that a module is Kotlin (JavaScript) after project creation?
    Create Kotlin (JavaScript) framework in Project structure -> Modules

    > e.g. importing a maven project defaults to Kotlin (Java) and I can’t work out how to force it to JavaScript.
    We’ll fix it soon.


    #6

    Hi bashor!

    Thanks for the detailed response!

    > doesn’t understand properties defined in prototypes.
    I think in your case you can workaround it by declaring things as native trait.

    This certainly works for some cases, but not all. e.g. If I define a component’s properties in Kotlin using inheritence or data classes then React will not find the properties. I can’t use native here as I need to instantiate them in Kotlin code. (Note: in practice I have a work-around for this).

    However, I think you’re right in general - something’s either native or Kotlin. Unfortunately with React there are grey areas as Kotlin defined components are instaniated and managed by the React JS runtime.

    > couldn't use enums JS backend supports top level enums sine M6.1 I replaced your LogLevel class by enum: http://kotlin-demo.jetbrains.com/?publicLink=113810345976161206390-137795029

    Hmmm ... it certainly failed in my scenario. I'll put it back in and see if I can reproduce the issue.

    > 2. The lack of documentation and/or examples on the JavaScript tool chain What kind of documentation are you missing?

    An example is worth a thousand words! I'd love a sample project that:

    • Is buildable by both the command line and the IDE
    • Creates a JS library module
    • Uses that JS library module in another module

    I just discovered https://github.com/abesto/pixi-kotlin the other day - this looks promising, but I haven't had time to look into it in detail.

    > 5. Nullity on signatures seems patchy. e.g. document.getElementById() and String.match() are both incorrectly marked as NotNull. Do you have a list of specific members that are incorrectly marked? We could fix at least most frequently used and revisit the whole issue later. Or you could just contribute fixes :)

    Sorry, I haven't kept track of them. And of course, you're right - I should do a pull request for those I do find. :8}

    > 6. I had to fall back to native JavaScript at times and there's no way of doing it without creating a companion JavaScript file (nothing like GWT's inline JavaScript snippets). We are working on fix ;) Could you provide your usecases? When would you like to use it?

    Here's where I've needed it: https://github.com/andrewoma/reakt/blob/master/todo/js/lib/reakt-bridge.js

    1. Creating a bridge to capture the 'this' pointer in JavaScript. React replaces the 'this' pointer of a specification with the 'this' pointer of a component. It seems completely dodgy to me, but such things are possible in JavaScript.
    2. Creating a Map implemented as object properties (there's no reason this couldn't be in the library).

    As to when I'd like to use it - NEVER.

    > 3. How are tests run? I noticed there's a QUnit asserter? Can I make use of any of this or is it just for the standard lib? Internally we use QUnit asserter, but we don't have generic way for tests in kotlin2js projects. I wouldn't recommend using this internal tool, because it will definitely change. What do you think would be the best approach to testing in Kotlin compiled to JS?

    The best approach would be to run JavaScript tests just like Java tests and have IntelliJ would sort out the details.

    The details get pretty ugly though:

    • If the code is pure Kotlin and uses no native libraries we could use the JVM backend and hope the JVM and JS backends are compatible
    • After that, you need a JavaScript runtime. On the JVM, Rhino or Nashorn might work for some cases.
    • Then you’ll need a JavaScript runtime with the DOM - this means leaving the JVM behind.
    • Finally, you’ll need to test on different browser/DOM implementations - something like Karma fills this space.


    For me, the holy grail would be:

    • IntelliJ seemlessly integrates with a headless webkit implementation to the point that I don’t know I’m running on it. i.e. debugging works, DOM operations work, etc
    • This would be used for development. Then something like a Karma runner plugin could be used for run on different DOM implementations.


    However, I suspect the easier route would be:

    • Leverage something like Karma to run the tests (it can be configured to run on various platforms and IntelliJ already supports it).
    • Add some “glue” to the test runner so that you can selectively run tests via Karma
    • Integrate debugging to the point that you could at least set a break point in IntelliJ and have execution stop (could leverage the existing Chrome debugging work).

    I have got Karma running Kotlin tests as a proof of concept (using QUnit). It seems to work fairly well. The challenges are:

    1. How to structure the tests so they work both on the JVM and JS backends (if the library is for both)
    2. The Karma runner runs all tests - there's no way of selectively running a test
    3. I haven't looked into debugging - I was vaguely hoping that using Karma's Chrome runner and IntelliJ's Chrome Debugger plugin might work together.

    I think that covers the main points.

    Thanks again for all your answers.

    Cheers,
    Andrew


    #7

    Hi Andrew,

    are you planning to continue with this library?


    #8

    Hi Jayson,

    I’ve decided to put in on hold until the JS toolchain matures. In particular:

    1. You can’t distribute a JS library at the moment (although it seems this is coming soon: https://devnet.jetbrains.com/thread/458600?tstart=0)
    2. You can’t build a JS library from the command line (also soon: https://github.com/JetBrains/kotlin/pull/524)
    3. There’s no way to run individual unit tests in the IDE (no blessed way of running tests at all in fact)

      I also have a few nagging doubts:
      1. Compilation times were blowing out fairly rapidly and incremental compilation isn't supported for JS yet
      2. The JS size gets hefty if you include the standard library (and will get even larger as 3rd party libraries are supported). Some kind of dead code removal seems necessary.

      So it's really about the toolchain maturity. The React API wrapped in Kotlin is great - especially the ability to remove JSX and replace it with clean Kotlin code.

      I’ll wait and see how the toolchain progresses and decided whether to invest more time into it in the future.

      (I’m also currently busy writing a Kotlin SQL library, so that’s my focus at present).

      Cheers,
      Andrew


      #9

      Thanks a lot, Andrew!