- 将 Kotlin/JS 项目迁移到 IR 编译器
- Convert JS- and React-related classes and interfaces to external interfaces
- Convert properties of external interfaces to var
- Convert functions with receivers in external interfaces to regular functions
- Create plain JS objects for interoperability
- Replace toString() calls on function references with .name
- Explicitly specify binaries.executable() in the build script
- Additional troubleshooting tips when working with the Kotlin/JS IR compiler
将 Kotlin/JS 项目迁移到 IR 编译器
We replaced the old Kotlin/JS compiler with the IR-based compiler in order to unify Kotlin’s behavior on all platforms and to make it possible to implement new JS-specific optimizations, among other reasons. You can learn more about the internal differences between the two compilers in the blog post Migrating our Kotlin/JS app to the new IR compiler by Sebastian Aigner.
Due to the significant differences between the compilers, switching your Kotlin/JS project from the old backend to the new one may require adjusting your code. On this page, we’ve compiled a list of known migration issues along with suggested solutions.
Install the Kotlin/JS Inspection pack plugin to get valuable tips on how to fix some of the issues that occur during migration.
Note that this guide may change over time as we fix issues and find new ones. Please help us keep it complete – report any issues you encounter when switching to the IR compiler by submitting them to our issue tracker YouTrack or filling out this form.
Convert JS- and React-related classes and interfaces to external interfaces
Issue: Using Kotlin interfaces and classes (including data classes) that derive from pure JS classes, such as React’s State and Props, can cause a ClassCastException. Such exceptions appear because the compiler attempts to work with instances of these classes as if they were Kotlin objects, when they actually come from JS.
Solution: convert all classes and interfaces that derive from pure JS classes to external interfaces:
// Replace thisinterface AppState : State { }interface AppProps : Props { }data class CustomComponentState(var name: String) : State
// With thisexternal interface AppState : State { }external interface AppProps : Props { }external interface CustomComponentState : State {var name: String}
In IntelliJ IDEA, you can use these structural search and replace templates to automatically mark interfaces as external:
Convert properties of external interfaces to var
Issue: properties of external interfaces in Kotlin/JS code can’t be read-only (val) properties because their values can be assigned only after the object is created with js() or jso() (a helper function from kotlin-wrappers):
import kotlinx.js.jsoval myState = jso<CustomComponentState>()myState.name = "name"
Solution: convert all properties of external interfaces to var:
// Replace thisexternal interface CustomComponentState : State {val name: String}
// With thisexternal interface CustomComponentState : State {var name: String}
Convert functions with receivers in external interfaces to regular functions
Issue: external declarations can’t contain functions with receivers, such as extension functions or properties with corresponding functional types.
Solution: convert such functions and properties to regular functions by adding the receiver object as an argument:
// Replace thisexternal interface ButtonProps : Props {var inside: StyledDOMBuilder<BUTTON>.() -> Unit}
external interface ButtonProps : Props {var inside: (StyledDOMBuilder<BUTTON>) -> Unit}
Create plain JS objects for interoperability
Issue: properties of a Kotlin object that implements an external interface are not enumerable. This means that they are not visible for operations that iterate over the object’s properties, for example:
for (var name in obj)console.log(obj)JSON.stringify(obj)
Although they are still accessible by the name: obj.myProperty
external interface AppProps { var name: String }data class AppPropsImpl(override var name: String) : AppPropsfun main() {val jsApp = js("{name: 'App1'}") as AppProps // plain JS objectprintln("Kotlin sees: ${jsApp.name}") // "App1"println("JSON.stringify sees:" + JSON.stringify(jsApp)) // {"name":"App1"} - OKval ktApp = AppPropsImpl("App2") // Kotlin objectprintln("Kotlin sees: ${ktApp.name}") // "App2"// JSON sees only the backing field, not the propertyprintln("JSON.stringify sees:" + JSON.stringify(ktApp)) // {"_name_3":"App2"}}
Solution 1: create plain JavaScript objects with js() or jso() (a helper function from kotlin-wrappers):
external interface AppProps { var name: String }data class AppPropsImpl(override var name: String) : AppProps
// Replace thisval ktApp = AppPropsImpl("App1") // Kotlin object
// With thisval jsApp = js("{name: 'App1'}") as AppProps // or jso {}
Solution 2: create objects with kotlin.js.json():
// or with thisval jsonApp = kotlin.js.json(Pair("name", "App1")) as AppProps
Replace toString() calls on function references with .name
Issue: in the IR backend, calling toString() on function references doesn’t produce unique values.
Solution: use the name property instead of toString().
Explicitly specify binaries.executable() in the build script
Issue: the compiler doesn’t produce executable .js files.
This may happen because the default compiler produces JavaScript executables by default while the IR compiler needs an explicit instruction to do this. Learn more in the Kotlin/JS project setup instruction.
Solution: add the line binaries.executable() to the project’s build.gradle(.kts).
kotlin {js(IR) {browser {}binaries.executable()}}
Additional troubleshooting tips when working with the Kotlin/JS IR compiler
These hints may help you when troubleshooting problems in your projects using the Kotlin/JS IR compiler.
Make boolean properties nullable in external interfaces
Issue: when you call toString on a Boolean from an external interface, you’re getting an error like Uncaught TypeError: Cannot read properties of undefined (reading 'toString'). JavaScript treats the null or undefined values of a boolean variable as false. If you rely on calling toString on a Boolean that may be null or undefined (for example when your code is called from JavaScript code you have no control over), be aware of this:
external interface SomeExternal {var visible: Boolean}fun main() {val empty: SomeExternal = js("{}")println(empty.visible.toString()) // Uncaught TypeError: Cannot read properties of undefined (reading 'toString')}
Solution: you can make your Boolean properties of external interfaces nullable (Boolean?):
// Replace thisexternal interface SomeExternal {var visible: Boolean}
// With thisexternal interface SomeExternal {var visible: Boolean?}
