与 JavaScript 的互操作
Kotlin/Wasm allows you to use both JavaScript code in Kotlin and Kotlin code in JavaScript.
As with Kotlin/JS, the Kotlin/Wasm compiler also has interoperability with JavaScript. If you are familiar with Kotlin/JS interoperability, you can notice that Kotlin/Wasm interoperability is similar. However, there are key differences to consider.
Kotlin/Wasm is Alpha. It may be changed at any time. Use it in scenarios before production. We would appreciate your feedback in YouTrack.
Use JavaScript code in Kotlin
Learn how to use JavaScript code in Kotlin by using external
declarations, functions with JavaScript code snippets,
and the @JsModule
annotation.
External declarations
External JavaScript code is not visible in Kotlin by default.
To use JavaScript code in Kotlin, you can describe its API with external
declarations.
JavaScript functions
Consider this JavaScript function:
function greet (name) {
console.log("Hello, " + name + "!");
}
You can declare it in Kotlin as an external
function:
external fun greet(name: String)
External functions don't have bodies, and you can call it as a regular Kotlin function:
fun main() {
greet("Alice")
}
JavaScript properties
Consider this global JavaScript variable:
let globalCounter = 0;
You can declare it in Kotlin using external var
or val
properties:
external var globalCounter: Int
These properties are initialized externally. The properties can't have = value
initializers in Kotlin code.
JavaScript classes
Consider this JavaScript class:
class Rectangle {
constructor (height, width) {
this.height = height;
this.width = width;
}
area () {
return this.height * this.width;
}
}
You can use it in Kotlin as an external class:
external class Rectangle(height: Double, width: Double) : JsAny {
val height: Double
val width: Double
fun area(): Double
}
All declarations inside the external
class are implicitly considered external.
External interfaces
You can describe the shape of a JavaScript object in Kotlin. Consider this JavaScript function and what it returns:
function createUser (name, age) {
return { name: name, age: age };
}
See how its shape can be described in Kotlin with an external interface User
type:
external interface User : JsAny {
val name: String
val age: Int
}
external fun createUser(name: String, age: Int): User
External interfaces don't have runtime type information and are a compile-time-only concept. Therefore, external interfaces have some restrictions compared to regular interfaces:
- You can't use them on the right-hand side of
is
checks. - You can't use them in class literal expressions (such as
User::class
). - You can't pass them as reified type arguments.
- Casting with
as
to external interfaces always succeed.
External objects
Consider these JavaScript variables holding an object:
let Counter = {
value: 0,
step: 1,
increment () {
this.value += this.step;
}
};
You can use them in Kotlin as an external object:
external object Counter : JsAny {
fun increment()
val value: Int
var step: Int
}
External type hierarchy
Similar to regular classes and interfaces, you can declare external declarations to extend other external classes and implement external interfaces. However, you can't mix external and non-external declarations in the same type hierarchy.
Kotlin functions with JavaScript code
You can add a JavaScript snippet to Kotlin/Wasm code by defining a function with = js("code")
body:
fun getCurrentURL(): String =
js("window.location.href")
If you want to run a block of JavaScript statements, surround your code inside the string with curly brackets {}
:
fun setLocalSettings(value: String): Unit = js(
"""{
localStorage.setItem('settings', value);
}"""
)
If you want to return an object, surround the curly brackets {}
with parentheses ()
:
fun createJsUser(name: String, age: Int): JsAny =
js("({ name: name, age: age })")
Kotlin/Wasm treats calls to the js()
function in a special way, and the implementation has some restrictions:
- A
js()
function call must be provided with a string literal argument. - A
js()
function call must be the only expression in the function body. - The
js()
function is only allowed to be called from package-level functions. - The function return type must be provided explicitly.
- Types are restricted, similar to
external fun
.
The Kotlin compiler puts the code string into a function in the generated JavaScript file and imports it into WebAssembly format. The Kotlin compiler doesn't verify these JavaScript snippets. If there are JavaScript syntax errors, they are reported when you run your JavaScript code.
The
@JsFun
annotation has similar functionality and will likely be deprecated.
JavaScript modules
By default, external declarations correspond to the JavaScript global scope. If you annotate a Kotlin file with the
@JsModule
annotation, then all external declarations within it are imported from the specified module.
Consider this JavaScript code sample:
// users.mjs
export let maxUsers = 10;
export class User {
constructor (username) {
this.username = username;
}
}
Use this JavaScript code in Kotlin with the @JsModule
annotation:
// Kotlin
@file:JsModule("./users.mjs")
external val maxUsers: Int
external class User : JsAny {
constructor(username: String)
val username: String
}
Use Kotlin code in JavaScript
Learn how to use your Kotlin code in JavaScript by using the @JsExport
annotation.
Functions with the @JsExport annotation
To make a Kotlin/Wasm function available to JavaScript code, use the @JsExport
annotation:
// Kotlin/Wasm
@JsExport
fun addOne(x: Int): Int = x + 1
Kotlin/Wasm functions marked with the @JsExport
annotation are visible as properties on a default
export of the generated .mjs
module.
You can then use this function in JavaScript:
// JavaScript
import exports from "./module.mjs"
exports.addOne(10)
Type correspondence
Kotlin/Wasm allows only certain types in signatures of JavaScript interop declarations.
These limitations apply uniformly to declarations with external
, = js("code")
or @JsExport
.
See how Kotlin types correspond to Javascript types:
Kotlin | JavaScript |
---|---|
Byte , Short , Int , Char |
Number |
Float , Double , |
Number |
Long , |
BigInt |
Boolean , |
Boolean |
String , |
String |
Unit in return position |
undefined |
Function type, for example (String) -> Int |
Function |
JsAny and subtypes |
Any JavaScript value |
JsReference |
Opaque reference to Kotlin object |
Other types | Not supported |
You can use nullable versions of these types as well.
JsAny type
JavaScript values are represented in Kotlin using the JsAny
type and its subtypes.
The standard library provides representation for some of these types:
- Package
kotlin.js
:JsAny
JsBoolean
,JsNumber
,JsString
JsArray
Promise
- Package
org.khronos.webgl
:- Typed arrays, like
Int8Array
- WebGL types
- Typed arrays, like
- Packages
org.w3c.dom.*
:- DOM API types
You can also create custom JsAny
subtypes by declaring an external
interface or class.
JsReference type
Kotlin values can be passed to JavaScript as opaque references using the JsReference
type.
For example, if you want to expose this Kotlin class User
to JavaScript:
class User(var name: String)
You can use the toJsReference()
function to create JsReference<User>
and return it to JavaScript:
@JsExport
fun createUser(name: String): JsReference<User> {
return User(name).toJsReference()
}
These references aren't directly available in JavaScript and behave like empty frozen JavaScript objects.
To operate on these objects, you need to export more functions to JavaScript using the get()
method where you unwrap the
reference value:
@JsExport
fun setUserName(user: JsReference<User>, name: String) {
user.get().name = name
}
You can create a class and change its name from JavaScript:
import UserLib from "./userlib.mjs"
let user = UserLib.createUser("Bob");
UserLib.setUserName(user, "Alice");
Type parameters
JavaScript interop declarations can have type parameters if they have an upper bound of JsAny
or its subtypes. For example:
external fun <T : JsAny> processData(data: JsArray<T>): T
Exception handling
The Kotlin try-catch
expression can't catch JavaScript exceptions.
If you try to use a JavaScript try-catch
expression to catch Kotlin/Wasm exceptions, it looks like a
generic WebAssembly.Exception
without directly accessible messages and data.
Kotlin/Wasm and Kotlin/JS interoperability differences
Although Kotlin/Wasm interoperability shares similarities with Kotlin/JS interoperability, there are key differences to consider:
Kotlin/Wasm | Kotlin/JS | |
---|---|---|
External enums | Doesn't support external enum classes. | Supports external enum classes. |
Type extensions | Doesn't support non-external types to extend external types. | Supports non-external types. |
JsName annotation |
Only has an effect when annotating external declarations. | Can be used to change names of regular non-external declarations. |
js() function |
js("code") function calls are allowed as a single expression body of package-level functions. |
The js("code") function can be called in any context and returns a dynamic value. |
Module systems | Supports ES modules only. There is no analog of the @JsNonModule annotation. Provides its exports as properties on the default object. Allows exporting package-level functions only. |
Supports ES modules and legacy module systems. Provides named ESM exports. Allows exporting classes and objects. |
Types | Applies stricter type restrictions uniformly to all interop declarations external , = js("code") , and @JsExport . Allows a select number of built-in Kotlin types and JsAny subtypes. |
Allows all types in external declarations. Restricts types that can be used in @JsExport . |
Long | Type corresponds to JavaScript BigInt . |
Visible as a custom class in JavaScript. |
Arrays | Not supported in interop directly yet. You can use the new JsArray type instead. |
Implemented as JavaScript arrays. |
Other types | Requires JsReference<> to pass Kotlin objects to JavaScript. |
Allows the use of non-external Kotlin class types in external declarations. |
Exception handling | Can't catch JavaScript exceptions. | Can catch JavaScript Error via the Throwable type. It can catch any JavaScript exception using the dynamic type. |
Dynamic types | Does not support the dynamic type. Use JsAny instead (see sample code below). |
Supports the dynamic type. |
Kotlin/JS dynamic type for interoperability with untyped or loosely typed objects is not supported in Kotlin/Wasm. Instead of
dynamic
type, you can useJsAny
type:
// Kotlin/JS fun processUser(user: dynamic, age: Int) { // ... user.profile.updateAge(age) // ... } // Kotlin/Wasm private fun updateUserAge(user: JsAny, age: Int): Unit = js("{ user.profile.updateAge(age); }") fun processUser(user: JsAny, age: Int) { // ... updateUserAge(user, age) // ... }