We have been working on a few projects that need to expose Kotlin code through Kotlin/JS as an external JS library. You can add JS as an output target of an existing KMM-focused module, but there are some issues you'll need to consider that don't generally present challenges to a mobile-only project.
Note that this post assumes that you already have a Kotlin Multiplatform Mobile library project, and are planning to add Kotlin/JS support to it
A good f̵i̵r̵s̵t̵ zero-step (not a mandatory one) would be to make sure that your source sets are marked by getting
as per the Kotlin Gradle DSL standards. It only applies if you use Kotlin
based build scripts
.
I would strongly recommend moving to
Kotlin
based scripts If you're still usingGroovy
-based Gradle build scripts
This Multiplatform Gradle DSL reference is a helpful document to follow while writing a Gradle build script for KMP.
After this step, your build script would have source sets declared as below,
kotlin {
sourceSets {
val commonMain by getting { /* ... */ }
}
}
You may check out this commit where I made these changes for the KaMPKit
project
Now let's move to actual steps
Step 1
Make sure that you remove any clean
task from your project. Gradle's LifecycleBasePlugin
already brings the clean
task, so you want to avoid getting a compilation error later. You most likely have one in your root Gradle file that looks like this,
tasks.register<Delete>("clean") {
delete(rootProject.buildDir)
}
Add the JS target block with IR
compiler option to your kotlin
block, and add the nodejs
target and library
container inside that
We will discuss both options in detail later
kotlin {
// .... other targets
js(IR) {
nodejs()
binaries.library()
}
}
Add main
and test
source sets for JS
sourceSets {
// .... other source sets
val jsMain by getting
val jsTest by getting {
dependencies {
// you don't need this if you already have
// kotlin("test") as your `commonTest` dependency
implementation(kotlin("test-js"))
}
}
}
If you sync
the project now, it should sync successfully! (probably won't build yet)
Now add the actual JS
source folders.
Since we've already added JS
target, we can add the jsMain
and jsTest
directories using auto complete by right-clicking on src
--> new
--> Directory
Step 2
At this stage, your project might not compile if you have any code in commonMain
that Kotlin/JS does not support, or if it is missing JS equivalents. ./gradlew build
would most likely fail.
You now have two options,
1) Make sure all your common code compiles for JS, can be exported as JS library and add js
actual for any expect
declarations
2) Introduce a mobileMain
source set/folder and move all the existing common code there
I would suggest going with option (2)
because it is a path of the least resistance, and you would get more time to think about how you want to write JS-specific code and common code for all platforms later. You may already have a lot of existing code in commonMain
with various dependencies that might not be suitable to use on JS.
Native mobile platforms, Android
and iOS
tend to have similar needs and capabilities like SQL, local files, threads, serialization, etc. JS/web, on the other hand, are somewhat different in what you can do and often work differently. It makes sense then that any moderately functional library will need at least consideration of the conceptual differences and quite probably another layer (mobileMain
) to better separate features and dependencies between web
and native mobile
. The latter is generally what we recommend, because you’ll probably need to do that separation at some point anyway.
option (1)
is the fallback if you have a small existing codebase that requires only a few changes to support the JS side, and if you would most likely not have any common code between android
and iOS
platforms.
For option 2
First, you would need to create custom source sets for mobileMain
and mobileTest
and make android
and ios
ones depend on it. Also, note that you will need to move all dependencies from commonMain
to mobileMain
. In short, you would want mobileMain
to look like your commonMain
after the change. commonMain
would get emptied.
Before and after diff of mentioned changes look like this for a sample project,
sourceSets {
- val commonMain by getting {
- dependencies {
- implementation("io.ktor:ktor-client-core:$ktorVersion")
- }
- }
+ val commonMain by getting
val commonTest by getting {
dependencies {
implementation(kotlin("test"))
}
}
val iosArm64Main by getting
val iosSimulatorArm64Main by getting
val iosMain by creating {
- dependsOn(commonMain)
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
}
val iosArm64Test by getting
val iosSimulatorArm64Test by getting
val iosTest by creating {
- dependsOn(commonTest)
iosX64Test.dependsOn(this)
iosArm64Test.dependsOn(this)
iosSimulatorArm64Test.dependsOn(this)
}
+ val mobileMain by creating {
+ dependsOn(commonMain)
+ androidMain.dependsOn(this)
+ iosMain.dependsOn(this)
+ dependencies {
+ implementation("io.ktor:ktor-client-core:$ktorVersion")
+ }
+ }
+ val mobileTest by creating {
+ dependsOn(commonTest)
+ androidTest.dependsOn(this)
+ iosTest.dependsOn(this)
+ }
val jsMain by getting
val jsTest by getting
}
Next, you would add the actual folder, just like what we did for js
above in step 1
. Along with that, you would want to move the entire commonMain
code content to mobileMain
, and commonTest
to mobileTest
.
After this, your project should build successfully with ./gradlew build
because you would not have any code in commonMain
that failed before due to introduction of JS side.
Step 3
Now you're ready to work on the JS codebase.
You might end up moving some classes back from mobileMain
to commonMain
depending on what you want in all three platforms, but at this point you can build and test the project after every step so that you're sure nothing is breaking.
Now that you have the JS
sourceSet, in the next post we will look at writing exportable code in Kotlin for JS using @JsExport
Thanks for reading! Let me know in the comments if you have questions. Also, you can reach out to me at @shaktiman_droid on Twitter, LinkedIn or Kotlin Slack. And if you find all this interesting, maybe you'd like to work with or work at Touchlab.
Top comments (0)