Hi there,
I work at Clue (helloclue.com), and we are currently migrating some library from Typescript to Kotlin. This library is an NPM module that runs in multiple environments, namely in an iOS App, an Android App and our Node backend. We started looking for an alternative technology as there seems to be no satisfying way to run Javascript code on the Android clients. We picked Kotlin as it is native on Android and compiles down to Javascript too, allowing us to preserve backwards-compatibility for existing clients.
In our configuration, we have a multi-project Gradle build:
- a “shared” project with the Kotlin sources and the related unit tests. It has a specific “shared” sourceSet that is standard-library-agnostic and that can be imported in the JS build.
- a “js” project that compiles the “shared” sourceSet of the “shared” project to Javascript, and assembles an NPM package
- a “typescript” project that contains all of the legacy sources and that depends on the build of the “js” project.
Migration pain points
Kotlin seems to deliver its promises, the migration is so far a success. Congratulations for the solidity of this early version
I still want to share some pain points. Indeed, some issues I encountered might already have a solution. The current tooling around Kotlin-JS seems to be targeting Web development. As we’re instead building and publishing a Node package, I encountered a number of specific needs.
Typescript type definition
We need to provide Typescript type definitions along with the JS sources. At the moment, we maintain them manually, but that’s a lot of hassle for something that actually looks easy to automate.
package.json
We need to distribute our NPM package with a package.json file describing the right dependencies (at least kotlin, in the correct version). kotlin-frontend-plugin can generate such a package.json file, but only if at least one NPM dependency is specified. Also, most of the fields receive dummy/invalid values that can’t be configured. So it doesn’t seem to be the right tool for our needs. For now, we use a simple groovy template for this purpose.
The fact that the latest kotlin
library on npmjs is in version 1.1.0
, and therefore different from the version of kotlin
we specify in our build (1.1.2-2
), doesn’t help.
Companion object
Being able to expose the functions of the companion object as static functions on the JS classes would be really cool. This would help us preserve backwards-compatibility as we migrate our code to Typescript.
Java packages
It is a best practice in the Java world to organise sources in packages with extensive names (com.helloclue.projectname.*), but this is not the case in Javascript as imports work very differently. I’ve been looking for a way to get rid of that namespace for the JS build.
Mixing Kotlin and Javascript sources
To solve the issue mentioned above, it could have been useful to be able to have a Javascript sourceSet taken in account by the kotlin2js Gradle plugin. This feature doesn’t seem to be available - at least it’s not documented.
Incompatible standard libraries
Math
is not visible with the Kotlin JS standard library. Our workaround was to create a typealias
from Math
to kotlin.js.Math
in our JS build.
I haven’t searched very far here, but it seems like Date
s, number formats… are not necessarily behaving in the same way in the Java build and the JS build. I wonder if this is on purpose (APIs seen as too platform-specific) or if this will be improved in future versions.
The annotation @JsName
is not available in the “Java” standard library, so we can’t use directly the annotation on our shared code. Our workaround was here also to create a typealias.
@JsName
required on all functions
@JsName is supposed to be useful in case of function overload. However, it seems like the compiler will always add a suffix to the function name if the annotation is omitted. As a result, we have to add the annotation on each and every function of our codebase. This seems to be a bug.
Conclusion
We’re moving ahead with Kotlin, as it is the best solution to our use-case. None of the elements mentioned above is blocking. Thanks to all of the Kotlin team for the great work