Generate Javascript library from Multiplatform project

I have a multiplatform project with iOS and Android apps set up as part of the project, but would like to export a Javascript library to be used within a React project. Is this possible at all, or does the web project need to be written in KotlinJS in order to use shared code?

In the multiplatform shared code, I have defined models and a HTTP Api client using Ktor. I have read the docs about the @JsExport and it says suspend functions can’t be exported, does that mean that this Ktor API client can’t be exported as a native JS library?

Here is the build.gradle.kts file for the shared module:

import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    kotlin("multiplatform")
    kotlin("native.cocoapods")
    id("kotlinx-serialization")
    id("com.android.library")
    id("com.rickclephas.kmp.nativecoroutines")
}

version = "1.0"

kotlin {
    android()
    jvm()

    js(IR) {
        useCommonJs()
        browser()
    }

    val iosTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget = when {
        System.getenv("SDK_NAME")?.startsWith("iphoneos") == true -> ::iosArm64
        System.getenv("NATIVE_ARCH")?.startsWith("arm") == true -> ::iosSimulatorArm64
        else -> ::iosX64
    }

    iosTarget("ios") {}

    cocoapods {
        framework {
            summary = "Some description for the Shared Module"
            homepage = "Link to the Shared Module homepage"
            baseName = "shared"
            ios.deploymentTarget = "14.1"
            podfile = project.file("../iosApp/Podfile")
        }
    }

    sourceSets {

        val commonMain by getting {
            dependencies {
                with(Ktor) {
                    implementation(clientCore)
                    implementation(clientJson)
                    implementation(clientLogging)
                    implementation(clientSerialization)
                }

                with(KotlinX) {
                    implementation(serializationCore)
                    implementation(coroutinesCore)
                }
            }
        }

        val commonTest by getting {
            dependencies {
                implementation(kotlin("test-common"))
                implementation(kotlin("test-annotations-common"))
            }
        }

        val androidMain by getting {
            dependencies {
                with(Ktor) {
                    implementation(clientAndroid)
                }
            }
        }

        val androidTest by getting {
            dependencies {
                implementation(kotlin("test-junit"))
                implementation("junit:junit:4.13.2")
            }
        }

        val iosMain by getting {
            dependencies {
                with(Ktor) {
                    implementation(clientIos)
                }
                implementation(KotlinX.coroutinesCore) {
                    version {
                        strictly(Version.coroutines)
                    }
                }
            }
        }

        val iosTest by getting

        val jvmMain by getting {
            dependencies {
                with(Ktor) {
                    implementation(clientJava)
                }
            }
        }

        val jsMain by getting {
            dependencies {
                with(Ktor) {
                    implementation(clientJs)
                }
            }
        }
    }
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        jvmTarget = "11"
    }
}

kotlin {
    sourceSets["jvmMain"].dependencies {
        implementation(Ktor.clientJava)
    }
}

android {
    compileSdk = Version.max_sdk
    sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
    defaultConfig {
        minSdk = Version.min_sdk
        targetSdk = Version.max_sdk
    }
}

Maybe wrapping the suspend functions to functions returning a Promise will make it exposable to a native JS library so that React/JS code can use it as a Promise

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.promise
import kotlin.js.Promise

suspend fun thisIsSuspending(): Boolean {
    return true
}

fun thisIsSuspendingPromise(): Promise<Boolean> = GlobalScope.promise { thisIsSuspending() }

(Promises can be converted to Kotlin Deferred and vice versa)

Or do something better than GlobalScope, this is just a quick example

@acclimate535

Thank you for the answer! This seems like a solution to my problem.

I have one more question. Which Gradle command should I run to generate a native JS library from my KMP project (after I wrap methods with Promise and @JsExport annotation), and where would the output file be?

Were you able to create a library that can be imported from a JS Project? I have a similar use case, but haven’t found much information…