@ kosexinをJSに露出させるjsexportガイド
Note that this post focuses on JS output for Kotlin. There is also a Typescript output (.d.ts file) with some unique issues that this post doesn't cover in detail.
前に、我々は追加
Kotlin/JS
既存のKMMライブラリへのサポート.さて、JS側で動作するコードを追加します.目次
Usage
Limitations
Interface
用途 It is critical to understand @JsExport あなたが外部のJSライブラリとしてKotlin/JSを通してKotlinコードを公開するならば、注釈とそれのあたりのすべての問題
新しいIR compiler , デフォルトでは、Javmlcriptの宣言はJavaScriptに公開されません.kotlin宣言をJavaScriptに見えるようにするには@ jsexportで注釈をつけなければなりません.
Note that @JsExport is experimental as of the posted date of this post (with Kotlin 1.6.10)
非常に基本的な例から始めましょう.
// commonMain - Greeting.kt
class Greeting {
fun greeting(): String {
return "Hello World!"
}
}
この時点で生成される.js
ライブラリファイルには、グリーティングクラスへの参照はありません.理由は、それが行方不明であるということです@JsExport
注釈.You can generate JS library code via
./gradlew jsBrowserDistribution
. You would find the.js, .d.ts and map
file inroot/build/js/packages/<yourlibname>/kotlin
folder.
さて、注釈を追加してJSコードを生成します.
import kotlin.js.ExperimentalJsExport
import kotlin.js.JsExport
@ExperimentalJsExport
@JsExport
class Greeting {
fun greeting(): String {
return "Hello World!"
}
}
The .js
and .d.ts
ファイルは、現在、挨拶参照を含みます.生成.jsファイル
function Greeting() {
}
Greeting.prototype.greeting = function () {
return 'Hello World!';
};
Greeting.$metadata$ = {
simpleName: 'Greeting',
kind: 'class',
interfaces: []
};
生成.D . TSファイル
export namespace jabbar.jigariyo.kmplibrary {
class Greeting {
constructor();
greeting(): string;
}
}
今すぐ呼び出すことができますGreeting
JavaScriptからconsole.log(new jabbar.jigariyo.kmplibrary.Greeting().greeting())
// Hello World!
Note that you would have to use fully qualified Kotlin names in JavaScript because Kotlin exposes its package structure to JavaScript.
あなたのExportableオブジェクトのすべての公的な属性がまた、移植可能である必要があることを心に留めておくことは重要です.
次の例では
CustomObj
また、輸出に輸出可能である必要があるでしょうMyDataClass
,@JsExport
data class MyDataClass(
val strVal: String,
val customObj: CustomObj // This would need to be exportable
)
@実験的なjsexport対@jsexport
@JsExport
is the annotation you need to tell the compiler to generate JavaScript code, and @ExperimentalJsExport
is an opt-in marker annotation to use @JsExport
as it is experimental to use.
You can get rid of the requirement of adding @ExperimentalJsExport
in code by declaring it as OptIn
in languageSettings
for all source sets in your kotlin
block.
kotlin {
sourceSets {
all {
languageSettings.apply {
optIn("kotlin.js.ExperimentalJsExport")
}
}
}
}
制限
As of Kotlin 1.6.10
, there are heavy limitations on what Kotlin types one can export to JavaScript.
You will most likely face one of these limitations if you add JS support in an existing KMP library.
Whenever something is not-exportable
, you would get either an error or a warning:
- Code does not compile with such errors
- Code compiles with such warnings, but you might have run-time issues
コレクション
Kotlin's collections APIs are not exportable, so you would have to come up with different strategies to deal with them. Some examples would be:
マップ
削除する必要があります
Map
使用方法common
また、JSにエクスポートするコード、またはmobile
and js
側.あなたはkotlin.js.Json
オブジェクトをjsMain
その後、それを地図にKotlin
必要なときにマップ.JS固有の実装については、Record からkotlin-extensions 図書館.
リスト
を置き換えることができます
List
による使用Array
すべてのプラットフォームに対して同じコードを保持する.これは単純な置換ではないかもしれません.例えば、
Array
API応答を解析するためにオブジェクトで使用されるならば、働きます.は、Array
インData
クラスは自分自身を提供する必要がありますequals
and hashcode
実装.Note that moving from
List
toArray
might have an impact on generated code foriOS
.List
becomesNSArray
on iOS side butArray
becomes a Kotlin object wrapping the array
あなたが別々の実現を望むならば
jsMain
, then kotlin-extensions
ライブラリはいくつかの役に立つJS固有のクラスを提供しますIterator, Set, and ReadOnlyArray ロング
Long
is not mapped to anything as there is no equivalent in the JavaScript
world. You would see the non-exportable
warning if you export Long
via Kotlin
.
If you ignore the warning, then Long
still kinda works. It just takes any value from JS. Kotlin will receive the input as Long
if JavaScript code sends a BigInt
.
It will not work for Typescript
unless you set skipLibCheck = true
in the config as type kotlin.Long
is not available.
// Kotlin
@JsExport
class Greeting {
@Suppress("NON_EXPORTABLE_TYPE")
fun printLong(value: Long) {
print(value)
}
}
// Generated .js
Greeting.prototype.printLong = function (value) {
print(value);
};
// Generated .d.ts
printLong(value: kotlin.Long): void;
// Usage from JS
const value = "0b11111111111111111111111111111111111111111111111111111"
Greeting().printLong(BigInt(value)) // This works
You can use @Suppress("NON_EXPORTABLE_TYPE") to suppress the exportable warning
インターフェース
Kotlin interfaces are not exportable. It gets annoying when a library has an interface-driven design, where it exposes the interface in public API rather than a specific implementation.
Interfaces will be exportable starting upcoming Kotlin 1.6.20! We would have to play around with that to see it working.
インタフェースを作るための回避策があります
JavaScript
.インターフェイスを回避する例を次に示します.
実装クラスの使用
@JsExport
interface HelloInterface {
fun hello()
}
The above code would show the non-exportable error. You can use the interface
indirectly via its implementation class to work around that problem.
@JsExport
object Hello : HelloInterface {
override fun hello() {
console.log("HELLO from HelloInterface")
}
}
Generated JS code for the above
hello
method will have amangled
name. Read more about it in code-mangling section
interface HelloInterface {
@JsName("hello")
fun hello()
}
@JsExport
object Hello : HelloInterface {
override fun hello() {
console.log("HELLO from HelloInterface")
}
}
同様に、ここで使用するいくつかのバリエーションがありますHelloInterface
,// Variation (2)
@JsExport
object HelloGet {
fun getInterface(): HelloInterface {
return Hello
}
}
// Variation (3)
@JsExport
class HelloWrapper(@JsName("value") val value: HelloInterface)
// Variation (4)
@JsExport
data class HelloWrapperData(@JsName("value") val value: HelloInterface)
上記のすべてのバリエーションはJS
Aでさえ側non-exportable
インタフェースの使用状況の警告/**
* JS side calling code
* (1)
* Hello.hello()
*
* (2)
* HelloGet.getInterface().hello()
*
* (3)
* const wrapperObj = HelloWrapper(Hello)
* wrapperObj.value.hello()
*
* (4)
* const wrapperDataObj = HelloWrapperData(Hello)
* wrapperDataObj.value.hello()
*/
Using Expect-Actual Pattern
Another idea for using interfaces is to use the expect-actual
pattern to define a Kotlin interface in common
and mobile
platforms and define an external interface
for the JS side. This approach might not scale well but can be very useful for simple cases.
// commonMain
expect interface Api {
fun getProfile(callback: (Profile) -> Unit)
}
// jsMain
// Here external makes it a normal JS object in generated code
actual external interface Api {
actual fun getProfile(callback: (Profile) -> Unit)
}
// mobileMain
actual interface Api {
actual fun getProfile(callback: (Profile) -> Unit)
}
These examples showcase workarounds that might or might not work for a particular project.
開花
As of Kotlin 1.6.10, enums are not exportable. It can create issues for projects that have a lot of existing enums.
Good news is that its support coming in Kotlin 1.6.20
There is also a trick to export and use enums on JS. It requires defining a JS-specific object with attributes that point to actual enums.
For example, this code won't compile,
@JsExport
enum Gender {
MALE,
FEMALE
}
Instead, you can do this indirectly by re-defining them through object fields. It works with a non-exportable warning. Note the warning suppression with annotation.
@Suppress("NON_EXPORTABLE_TYPE")
@ExperimentalJsExport
@JsExport
object GenderType {
val male = Gender.MALE
val female = Gender.FEMALE
}
密閉クラス
Sealed classes are exportable, but they’re buggy as of Kotlin 1.6.10
You can export a data or regular class as subclasses inside a Sealed class body, but not an object.
@JsExport
sealed class State {
object Loading: State() // This won't be visible
data class Done(val value: String): State() // This would be visible
}
You can work around this problem by moving the subclasses outside the body of the sealed class, but then you cannot write it like State.Loading
. It is more of a readability issue in that case.
Also, sealed classes have known issues with typescript binding as well.
コードマンリング
The Kotlin compiler mangles the names of the functions and attributes. It can be frustrating to deal with mangled names.
For example,
@JsExport
object Hello : HelloInterface {
override fun hello() {
console.log("HELLO from HelloInterface")
}
}
Generated JS code for hello
method looks like,
Hello.prototype.hello_sv8swh_k$ = function () {
console.log('HELLO from HelloInterface');
};
_something_0, _value_3
JS側では、それを介して制御名を提供する必要がある記号です@JsName
注釈Kotlin
側.追加後
@JsName("hello")
上の例では、生成されたコードがこのように見えますhello
リファレンスメソッドhello_sv8swh_k$
内部的にはHello.prototype.hello_sv8swh_k$ = function () {
console.log('HELLO from HelloInterface');
};
Hello.prototype.hello = function () {
return this.hello_sv8swh_k$();
};
Note that
@JsName
is prohibited for overridden members, so you would need to set it to a base class property or method.
浮遊関数
You cannot expose suspended functions to JS. You would need to convert them into JavaScript Promise
object.
The easiest way to do that would be to wrap suspend calls inside,
GlobalScope.promise {
// suspend call
}
This function comes from Promise.kt
in the coroutine library
. It returns a generic type.
As mentioned earlier, some of these issues would get resolved with Kotlin 1.6.20, so keep that in mind.
In the next post, we will look at different ways to distribute Kotlin/JS library since we've some JS exportable code.
Thanks for reading! Let me know in the comments if you have questions. Also, you can reach out to me at @ on Twitter, or Kotlin Slack . そして、あなたがこのすべてを見つけるならば、多分あなたはwork with or work at TouchLabReference
この問題について(@ kosexinをJSに露出させるjsexportガイド), 我々は、より多くの情報をここで見つけました https://dev.to/touchlab/jsexport-guide-for-exposing-kotlin-to-js-20l9テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol