Skip to content

Kotlin/Wasm support#1099

Merged
cedrickcooke merged 10 commits intomainfrom
cedrickc/kotlin-wasm-support
Jan 27, 2026
Merged

Kotlin/Wasm support#1099
cedrickcooke merged 10 commits intomainfrom
cedrickc/kotlin-wasm-support

Conversation

@cedrickcooke
Copy link
Contributor

@cedrickcooke cedrickcooke commented Jan 20, 2026

Adds support for Kotlin/Wasm. Tested that Sensortag (see: JuulLabs/sensortag#531) works on both JS and WasmJS targets.

Highest risk in this PR is probably around exception handling. I think I got the pattern correct, it's just the place I'm least confident about and hard to test.

Solves #765

@cedrickcooke cedrickcooke requested review from a team and twyatt as code owners January 20, 2026 23:36
@cedrickcooke cedrickcooke added javascript major Changes that should bump the MAJOR version number labels Jan 20, 2026
@cedrickcooke cedrickcooke linked an issue Jan 21, 2026 that may be closed by this pull request
@twyatt twyatt self-requested a review January 23, 2026 00:07
Comment on lines +6 to +7
internal fun JsArray<*>.isEmpty() = length == 0
internal fun JsArray<*>.isNotEmpty() = !isEmpty()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does JsArray not already have these functions?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not in webMain or wasmMain. You do get them in jsMain (where it's just a typealias for Array), but the common interface is quite limited.

/*
 * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package kotlin.js

/** JavaScript Array for WasmJs Interop */
@SinceKotlin("2.2")
@ExperimentalWasmJsInterop
public expect class JsArray<T : JsAny?> : JsAny

@SinceKotlin("2.2")
@ExperimentalWasmJsInterop
public expect fun <T: JsAny?> JsArray(): JsArray<T>

@SinceKotlin("2.2")
@ExperimentalWasmJsInterop
public expect val JsArray<*>.length: Int

@SinceKotlin("2.2")
@ExperimentalWasmJsInterop
public expect operator fun <T : JsAny?> JsArray<T>.get(index: Int): T?

@SinceKotlin("2.2")
@ExperimentalWasmJsInterop
public expect operator fun <T : JsAny?> JsArray<T>.set(index: Int, value: T)

/** Returns a new [Array] containing all the elements of this [JsArray]. */
@SinceKotlin("2.2")
@ExperimentalWasmJsInterop
public expect fun <T : JsAny?> JsArray<T>.toArray(): Array<T>

/** Returns a new [JsArray] containing all the elements of this [Array]. */
@SinceKotlin("2.2")
@ExperimentalWasmJsInterop
public expect fun <T : JsAny?> Array<T>.toJsArray(): JsArray<T>

/** Returns a new [List] containing all the elements of this [JsArray]. */
@SinceKotlin("2.2")
@ExperimentalWasmJsInterop
public expect fun <T : JsAny?> JsArray<T>.toList(): List<T>

/** Returns a new [JsArray] containing all the elements of this [List]. */
@SinceKotlin("2.2")
@ExperimentalWasmJsInterop
public expect fun <T : JsAny?> List<T>.toJsArray(): JsArray<T>

Copy link
Member

@twyatt twyatt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing job on this!!! 💯

Comment on lines +7 to +12
private val watchAdvertisements: JsAny? =
js("BluetoothDevice.prototype.watchAdvertisements")

private val unwatchAdvertisements: JsAny? =
js("BluetoothDevice.prototype.unwatchAdvertisements")

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should probably be marked with get() to keep the previous lazy behavior in associated can.. vals?

Suggested change
private val watchAdvertisements: JsAny? =
js("BluetoothDevice.prototype.watchAdvertisements")
private val unwatchAdvertisements: JsAny? =
js("BluetoothDevice.prototype.unwatchAdvertisements")
private val watchAdvertisements: JsAny?
get() = js("BluetoothDevice.prototype.watchAdvertisements")
private val unwatchAdvertisements: JsAny?
get() = js("BluetoothDevice.prototype.unwatchAdvertisements")

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure how to handle these. Trying to keep the get() = syntax produces the following error:

e: <path>/WatchingAdvertisementsSupport.kt:8:13 Calls to 'js(code)' must be a single expression inside a top-level function body or a property initializer in Kotlin/Wasm.

I erred on the side of call it once and store the value. I could change it over to a fun which would preserve behavior more closely, but it's kind of ugly to add the parenthesis? 🤷 Happy to change it if you prefer.

jna = "5.18.1"
jvm-toolchain = "17"
kotlin = "2.2.20"
kotlin = "2.2.21"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine, just curious if this bump was needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2.2.21 contained some fixes around exception handling for WASM in Safari based browsers

Comment on lines +11 to +12
/** Wrapper around `kotlinx.coroutines.await` which exists in both js and wasm, but has no expect-actual. */
internal expect suspend fun <T : JsAny?> Promise<T>.await(): T
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, wild that this isn't provided for us in webMain world. 🤦

@cedrickcooke cedrickcooke enabled auto-merge (squash) January 27, 2026 19:59
@cedrickcooke cedrickcooke merged commit 64d3913 into main Jan 27, 2026
10 checks passed
@cedrickcooke cedrickcooke deleted the cedrickc/kotlin-wasm-support branch January 27, 2026 20:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

javascript major Changes that should bump the MAJOR version number

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add no-op WASM target artifact

3 participants