Search code examples
typescriptkotlingradlenpmkotlin-multiplatform

npm link packages generated by KotlinJS


I've created a Kotlin Multiplatform application that generates a JS and TypeScript output file, i'm trying to make use of those output files inside a TypeScript project I'm fairly new to TypeScript as well as the whole npm ecosystem

enter image description here

The first package.json inside the hotel directory looks like this:

{
  "main": "kotlin/hotel.js",
  "devDependencies": {
    "webpack": "4.42.1",
    "webpack-cli": "3.3.11",
    "source-map-loader": "0.2.4",
    "webpack-dev-server": "3.10.3",
    "css-loader": "3.4.2",
    "style-loader": "1.1.3"
  },
  "dependencies": {
    "kotlin": "file:/home/vlad/Code/hotel-state/build/js/packages_imported/kotlin/1.4.0-M2",
    "kotlin-source-map-loader": "file:/home/vlad/Code/hotel-state/build/js/packages_imported/kotlin-source-map-loader/1.4.0-M2"
  },
  "peerDependencies": {},
  "optionalDependencies": {},
  "bundledDependencies": [],
  "name": "hotel",
  "version": "1.0.0"
}

The second package.json inside the js directory looks like this:

{
  "private": true,
  "workspaces": [
    "packages/hotel",
    "packages/hotel-test"
  ],
  "devDependencies": {},
  "dependencies": {},
  "peerDependencies": {},
  "optionalDependencies": {},
  "bundledDependencies": [],
  "name": "hotel",
  "version": "1.0.0"
}

From the hotel directory, i've done

sudo npm link hotel 

In my second project, i now add this to the package.json followed by an npm install

  "dependencies": {
    "hotel": "file:/usr/lib/node_modules/hotel",
    "lit-html": "^1.2.1"
  },

enter image description here

When i hit autocomplete, i can see the hotel package, but it keeps on showing a red line underneath it. When i try to use it in my other TypeScript files, it stops compiling

The typescript headers in that hotel.d.ts has a namespace

declare namespace hotel {
    type Nullable<T> = T | null | undefined
    namespace com.harakati.hotel {
        /* ErrorDeclaration: Class com.harakati.hotel.TH with kind: OBJECT */
    }

    namespace com.harakati.hotel {
        class Room {
            constructor(roomNumber: Nullable<number>)
            static Room_init_$Create$(roomNumber: Nullable<number>, $mask0: number, $marker: Nullable<any /*Class kotlin.js.DefaultConstructorMarker with kind: OBJECT*/>): com.harakati.hotel.Room
            roomNumber: number;
        }
    }

Am i doing the linking correctly?

Am i including this linked package correctly?

Is there anythin else that i might be missing?


Solution

  • Turns out the generated TypeScript headers in Kotlin 1.4 M2 aren't being exported and somebody has already logged a bug for it

    https://youtrack.jetbrains.com/issue/KT-37883

    A quick and dirty workaround i wrote is to run some Kotlin script at the end of a Gradle build to post-process the generated TypeScript.

    Include this inside the Kotlin block in your build.gradle.kts and the generated TS headers will import without problems in the TS project.

        gradle.buildFinished {
            println("==============================================================")
            val path = "$buildDir/js/packages/${project.name}/kotlin/${project.name}.d.ts"
            println("Making some adjustments to \n$path")
            val newLines = mutableListOf<String>()
            println("==============================================================")
            if (File(path).exists()) {
                File(path).readLines().forEach { line ->
                    if (line.startsWith("declare namespace") || line.startsWith("}")) {
                        "// $line".let {
                            newLines.add(it)
                            println(it)
                        }
                    } else if (line.trim().startsWith("namespace")) {
                        line.replace("namespace", "export namespace").let {
                            newLines.add(it)
                            println(it)
                        }
                    } else {
                        line.let {
                            newLines.add(it)
                            println(it)
                        }
                    }
                }
                File(path).writeText(newLines.joinToString("\n"))
            } else {
                println("$path does not exist")
            }
            println("==============================================================")
        }
    

    Inside package.json in your TypeScript project, include it becomes straight forward

      "dependencies": {
        "hotel": "/home/vlad/Code/.../hotel/build/js",
        "lit-html": "^1.2.1",
        "tslib": "^2.0.0"
      },
    

    or simply use npm link to create a simlink to the generated output which then allows you to point to point to the link instead of the absolute path which could change if you move stuff around.