12

I have just managed to import Kotlin compiled javascript module in an angular 6 typescript file. It was not easy and the result confuses me. I wanna know if more elegant way exists.

Originally I take a Kotlin file:

package com.example.test

data class SomeInterface(
    var id: String? = null,
    var value: String? = null
) {
}

It well compiles to the following JavaScript

(function (root, factory) {
  if (typeof define === 'function' && define.amd)
    define(['exports', 'kotlin'], factory);
  else if (typeof exports === 'object')
    factory(module.exports, require('kotlin'));
  else {
    if (typeof kotlin === 'undefined') {
      throw new Error("Error loading module 'TestKotlinCompiled'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'TestKotlinCompiled'.");
    }
    root.TestKotlinCompiled = factory(typeof TestKotlinCompiled === 'undefined' ? {} : TestKotlinCompiled, kotlin);
  }
}(this, function (_, Kotlin) {
  'use strict';
  var Kind_CLASS = Kotlin.Kind.CLASS;
  function SomeInterface(id, value) {
    if (id === void 0)
      id = null;
    if (value === void 0)
      value = null;
    this.id = id;
    this.value = value;
  }
  SomeInterface.$metadata$ = {
    kind: Kind_CLASS,
    simpleName: 'SomeInterface',
    interfaces: []
  };
  SomeInterface.prototype.component1 = function () {
    return this.id;
  };
  SomeInterface.prototype.component2 = function () {
    return this.value;
  };
  SomeInterface.prototype.copy_rkkr90$ = function (id, value) {
    return new SomeInterface(id === void 0 ? this.id : id, value === void 0 ? this.value : value);
  };
  SomeInterface.prototype.toString = function () {
    return 'SomeInterface(id=' + Kotlin.toString(this.id) + (', value=' + Kotlin.toString(this.value)) + ')';
  };
  SomeInterface.prototype.hashCode = function () {
    var result = 0;
    result = result * 31 + Kotlin.hashCode(this.id) | 0;
    result = result * 31 + Kotlin.hashCode(this.value) | 0;
    return result;
  };
  SomeInterface.prototype.equals = function (other) {
    return this === other || (other !== null && (typeof other === 'object' && (Object.getPrototypeOf(this) === Object.getPrototypeOf(other) && (Kotlin.equals(this.id, other.id) && Kotlin.equals(this.value, other.value)))));
  };
  var package$com = _.com || (_.com = {});
  var package$example = package$com.example || (package$com.example =     {});
  var package$test = package$example.test || (package$example.test = {});
  package$test.SomeInterface = SomeInterface;
  Kotlin.defineModule('TestKotlinCompiled', _);
  return _;
}));

In package.json I add "kotlin": "^1.2.70", to the dependencies section. In angular component I have to use such a code for import.

import * as TestKotlinCompiled from "../../generated/TestKotlinCompiled";

// @ts-ignore
const SomeInterface = TestKotlinCompiled.com.example.test.SomeInterface;
// @ts-ignore
type SomeInterface = TestKotlinCompiled.com.example.test.SomeInterface;

This is minimal mandatory code to use class SomeInterfac in the package com.example.test generated to the module TestKotlinCompiled.

The problems here are following.

// @ts-ignore is required because at the compile time the ts-comiler does not see the content of the module being imported.

const is required for new SomeInterface()

type is required for let x: SomeInterface;

All these look terribly hacky. I wold like something easier like import {SomeInterface} from '../../generated/TestKotlinCompiled' using namespace com.example.test without const and type. So, is there a way to simplify my above code?

2 Answers 2

2

I succeeded to improve little bit usability of KotlinJs in Angular. I dispose my experiments in https://github.com/svok/kotlin-multiplatform-sample

First, we must create a multiplatform submodule in Gradle. In that we generate js files (among other possible platforms).

Then we add to package.json...

{
  "dependencies": {
    "kotlin": "^1.3.21",
    "proj-common": "file:../build/javascript-compiled"
  }
}

proj-common is our compiled Kotlin module. The path there is where kotlin-js files are built to.

Thus, in typescript we just use one more npm module

import {sample} from 'proj-common/proj-common';

// For class Sample
sample = new sample.Sample();

// For object Platform
platform = sample.Platform;

Compilation goes well with no neсessity to use // @ts-ignore

Update

In the above explanation there was a problem with subdependencies. They were not exported, but not all subdependencies have their equivalents in npm repository. The below code solves this problem.

tasks {
    task<Sync>("assembleWeb") {
        val dependencies = configurations.get("jsMainImplementation").map {
            val file = it
            val (tDir, tVer) = "^(.*)-([\\d.]+-\\w+|[\\d.]+)\\.jar$"
                .toRegex()
                .find(file.name)
                ?.groupValues
                ?.drop(1)
                ?: listOf("", "")
            var jsFile: File? = null
            copy {
                from(zipTree(file.absolutePath), {
                    includeEmptyDirs = false
                    include { fileTreeElement ->
                        val path = fileTreeElement.path
                        val res = (path.endsWith(".js") || path.endsWith(".map"))
                                && (path.startsWith("META-INF/resources/") || !path.startsWith("META-INF/"))
                        if (res && path.endsWith(".js") && ! path.endsWith(".meta.js")) jsFile = fileTreeElement.file
                        res
                    }
                })
                into("$npmTarget/$tDir")
            }
            jsFile?.also { packageJson(tDir, it, tVer) }
            tDir to jsFile
        }
            .filter { it.second != null }
            .map { it.first to it.second!! }
            .toMap()

        packageJson(npmDir, File(jsOutputFile), project.version.toString(), dependencies)
        dependsOn("jsMainClasses")
    }

    assemble.get().dependsOn("assembleWeb")
}

fun packageJson(dir: String, jsFile: File, version: String, dependencies: Map<String, File> = emptyMap()) {
    val deps = dependencies.map {
        """"${js2Name(it.value)}": "file:../${it.key}""""
    }.joinToString(",\n            ")
    val text = """
        {
          "name": "${js2Name(jsFile)}",
          "version": "${version}",
          "main": "./${jsFile.name}",
          "dependencies": {
            ${deps}
          }
        }
    """.trimIndent()
    File("$npmTarget/$dir/package.json").apply {
        if (parentFile.exists()) {
            parentFile.delete()
        }
        parentFile.mkdirs()
        writeText(text)
    }
}

fun js2Name(jsFile: File) = jsFile.name.replace("""\.js$""".toRegex(), "")

Then, import from the front submodule:

{
  "dependencies": {
    "proj-common": "file:../build/npm"
  }
}

And in the typescript file:

import {sample} from 'proj-common';

// For class Sample
sample = new sample.Sample();

// For object Platform
platform = sample.Platform;

The sample project see at https://github.com/svok/kotlin-multiplatform-sample

Update 2

Now you can create full stack projects with kotlin common subproject as easy as just attaching a plugin in gradle

plugins {
    id("com.crowdproj.plugins.jar2npm")
}

This plugin will automatically inject all your kotlin-js jar packages into your node_modules during compilation.

The https://github.com/svok/kotlin-multiplatform-sample project is now rewritten with this plugin. See proj-angularfront submodule.

Sign up to request clarification or add additional context in comments.

2 Comments

You wrote just a wrapper for using angular. Not enough
I solved the problem of integration between Angular and Kotlin. If you want more, you are welcome to suggest your own solution. Up to now I didn't see that.
1

I too had a go and solving this integration, There are a number of issues to overcome, i.e.

  • generating typescript declaration files
  • unpacking kotlin JS modules into node_modules
  • third-party libraries

Blog post describing the issues is here https://medium.com/@dr.david.h.akehurst/building-applications-with-kotlin-and-typescript-8a165e76252c

I also created a gradle plugin that make it all alot easier, https://github.com/dhakehurst/net.akehurst.kotlin.kt2ts

1 Comment

Sorry, it didn’t like the [] round the links

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.