Kotlin/JS プロジェクトを IR コンパイラに移行する
私たちは、すべてのプラットフォームでKotlinの挙動を統一し、新しいJS固有の最適化を実装することなどを目的として、古いKotlin/JSコンパイラをIRベースのコンパイラに置き換えました。 これら2つのコンパイラの内部的な違いについては、Sebastian Aigner氏によるブログ記事Migrating our Kotlin/JS app to the new IR compilerで詳しく学ぶことができます。
コンパイラ間の著しい違いにより、Kotlin/JSプロジェクトを古いバックエンドから新しいバックエンドに切り替えるには、コードの調整が必要になる場合があります。このページでは、既知の移行問題とその解決策のリストをまとめました。
TIP
Kotlin/JS Inspection packプラグインをインストールすると、移行中に発生する問題の一部を修正するための役立つヒントが得られます。
このガイドは、問題の修正や新しい問題の発見に伴い、時間の経過とともに変更される可能性があることに注意してください。このガイドを完全な状態に保つため、ご協力をお願いします。IRコンパイラへの切り替え時に遭遇した問題は、課題トラッカーYouTrackに提出するか、このフォームに記入して報告してください。
JSおよびReact関連のクラスとインターフェースをexternalインターフェースに変換する
問題: ReactのState
やProps
など、純粋なJSクラスから派生したKotlinインターフェースおよびクラス(データクラスを含む)を使用すると、ClassCastException
が発生する可能性があります。このような例外は、実際にはJSから来ているにもかかわらず、コンパイラがこれらのクラスのインスタンスをKotlinオブジェクトであるかのように扱おうとするために発生します。
解決策: 純粋なJSクラスから派生するすべてのクラスとインターフェースをexternalインターフェースに変換します。
// Replace this
interface AppState : State { }
interface AppProps : Props { }
data class CustomComponentState(var name: String) : State
// With this
external interface AppState : State { }
external interface AppProps : Props { }
external interface CustomComponentState : State {
var name: String
}
IntelliJ IDEAでは、これらの構造検索と置換テンプレートを使用して、インターフェースを自動的にexternal
としてマークできます。
externalインターフェースのプロパティをvarに変換する
問題: Kotlin/JSコードのexternalインターフェースのプロパティは、読み取り専用(val
)プロパティにすることはできません。なぜなら、それらの値はjs()
またはjso()
(kotlin-wrappers
のヘルパー関数)でオブジェクトが作成された後にのみ代入できるためです。
import kotlinx.js.jso
val myState = jso<CustomComponentState>()
myState.name = "name"
解決策: externalインターフェースのすべてのプロパティをvar
に変換します。
// Replace this
external interface CustomComponentState : State {
val name: String
}
// With this
external interface CustomComponentState : State {
var name: String
}
externalインターフェース内のレシーバー付き関数を通常の関数に変換する
問題: external宣言は、拡張関数や対応する関数型を持つプロパティなど、レシーバー付き関数を含むことはできません。
解決策: そのような関数とプロパティを、レシーバーオブジェクトを引数として追加することで、通常の関数に変換します。
// Replace this
external interface ButtonProps : Props {
var inside: StyledDOMBuilder<BUTTON>.() -> Unit
}
external interface ButtonProps : Props {
var inside: (StyledDOMBuilder<BUTTON>) -> Unit
}
相互運用性のためにプレーンなJSオブジェクトを作成する
問題: externalインターフェースを実装するKotlinオブジェクトのプロパティは、_列挙可能_ではありません。これは、たとえば以下のような、オブジェクトのプロパティを反復処理する操作に対してそれらが見えないことを意味します。
for (var name in obj)
console.log(obj)
JSON.stringify(obj)
しかし、それらは名前(例:obj.myProperty
)でアクセスすることは引き続き可能です。
external interface AppProps { var name: String }
data class AppPropsImpl(override var name: String) : AppProps
fun main() {
val jsApp = js("{name: 'App1'}") as AppProps // plain JS object
println("Kotlin sees: ${jsApp.name}") // "App1"
println("JSON.stringify sees:" + JSON.stringify(jsApp)) // {"name":"App1"} - OK
val ktApp = AppPropsImpl("App2") // Kotlin object
println("Kotlin sees: ${ktApp.name}") // "App2"
// JSON sees only the backing field, not the property
println("JSON.stringify sees:" + JSON.stringify(ktApp)) // {"_name_3":"App2"}
}
解決策1: js()
またはjso()
(kotlin-wrappers
のヘルパー関数)を使用して、プレーンなJavaScriptオブジェクトを作成します。
external interface AppProps { var name: String }
data class AppPropsImpl(override var name: String) : AppProps
// Replace this
val ktApp = AppPropsImpl("App1") // Kotlin object
// With this
val jsApp = js("{name: 'App1'}") as AppProps // or jso {}
解決策2: kotlin.js.json()
でオブジェクトを作成します。
// or with this
val jsonApp = kotlin.js.json(Pair("name", "App1")) as AppProps
関数参照に対するtoString()呼び出しを.nameに置き換える
問題: IRバックエンドでは、関数参照に対してtoString()
を呼び出しても一意の値が生成されません。
解決策: toString()
の代わりにname
プロパティを使用します。
ビルドスクリプトでbinaries.executable()を明示的に指定する
問題: コンパイラが実行可能な.js
ファイルを生成しません。
これは、デフォルトのコンパイラはデフォルトでJavaScript実行可能ファイルを生成する一方で、IRコンパイラはこれを行うための明示的な指示を必要とするために発生する可能性があります。詳細については、Kotlin/JSプロジェクトセットアップ手順を参照してください。
解決策: プロジェクトのbuild.gradle(.kts)
にbinaries.executable()
の行を追加します。
kotlin {
js(IR) {
browser {
}
binaries.executable()
}
}
Kotlin/JS IRコンパイラを使用する際のその他のトラブルシューティングのヒント
これらのヒントは、Kotlin/JS IRコンパイラを使用しているプロジェクトで問題のトラブルシューティングを行う際に役立つかもしれません。
externalインターフェースのブーリアンプロパティをnull許容にする
問題: externalインターフェースのBoolean
に対してtoString
を呼び出すと、Uncaught TypeError: Cannot read properties of undefined (reading 'toString')
のようなエラーが発生します。JavaScriptはブーリアン変数のnull
またはundefined
の値をfalse
として扱います。もし(例えば、制御できないJavaScriptコードからコードが呼び出される場合など)null
またはundefined
である可能性があるBoolean
に対してtoString
を呼び出すことに依存している場合、これに注意してください。
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')
}
解決策: externalインターフェースのBoolean
プロパティをnull許容(Boolean?
)にすることができます。
// Replace this
external interface SomeExternal {
var visible: Boolean
}
// With this
external interface SomeExternal {
var visible: Boolean?
}