Seskar is a Gradle plugin that provides useful additions for Kotlin/JS projects.
To add Seskar to your project, you need to add the following configuration to your project's build.gradle.kts
:
plugins {
kotlin("multiplatform") version "2.1.0"
id("io.github.turansky.seskar") version "3.75.0"
}
// App.kt
suspend fun main() {
console.log("App start!")
val value = if (Random.nextDouble() > 0.5) {
createCalculationWithHeavyLibrary()
} else {
42
}
console.log("Value: $value")
}
// createCalculationWithHeavyLibrary.kt
/**
* - Function will be located in separate JS chunk
* - Chunk will be loaded when function will be called first time
*/
@Lazy
val createCalculationWithHeavyLibrary = LazyFunction<Int> {
val calculator = HeavyCalculator()
calculator.calculate()
}
// Content.kt
@Lazy
val Content = FC {
MyHeavyComponent1()
MyHeavyComponent2()
}
// App.kt
val App = FC {
Header()
Suspense {
Content()
}
Footer()
}
Seskar generates keys for child elements to prevent problems with conditional rendering.
As a result, in the following example Content
child state won't be reset after showHeader
property change.
val App = FC {
val showHeader = useShowHeader()
if (showHeader)
Header() // generated: key = "@rdk/5"
Content() // generated: key = "@rdk/7"
Footer() // generated: key = "@rdk/8"
}
When a project uses the Kotlin/JS compiler, value classes
are autoboxed. If a value class
is used as a dependency
of a React hook (e.g., in useMemo
, useState
or useEffect
), a new class will be created on every rendering pass,
which causes infinite re-rendering.
To prevent this, Seskar disables autoboxing for value class
dependencies in hooks.
It also converts Long
values to String
.
Seskar supports Duration
by default.
value class Count(
private val value: Int,
)
val Counter = FC {
val count: Count = useCount()
useEffect(count) {
println("Count changed: $count")
}
}
function Counter() {
var count = useCount()
useEffect(
() => {
println(`Count changed: $count`)
},
// AUTOBOXING
[new Count(count)],
)
}
function Counter() {
var count = useCount()
useEffect(
() => {
println(`Count changed: $count`)
},
// NO AUTOBOXING
[count],
)
}
Use enum constant as union value
// TypeScript
type Align = 'TOP' | 'LEFT' | 'BOTTOM' | 'RIGHT'
// Kotlin
sealed external interface Align {
companion object {
@JsValue("TOP")
val TOP: Align
@JsValue("LEFT")
val LEFT: Align
@JsValue("BOTTOM")
val BOTTOM: Align
@JsValue("RIGHT")
val RIGHT: Align
}
}
println(Align.TOP) // 'TOP'
println(Align.LEFT) // 'LEFT'
// TypeScript
type LayoutOrientation = 'top-to-bottom'
| 'left-to-right'
| 'bottom-to-top'
| 'right-to-left'
// Kotlin
import seskar.js.JsValue
sealed external interface LayoutOrientation {
companion object {
@JsValue("top-to-bottom")
val TOP_TO_BOTTOM: LayoutOrientation
@JsValue("left-to-right")
val LEFT_TO_RIGHT: LayoutOrientation
@JsValue("bottom-to-top")
val bottomToTop: LayoutOrientation
@JsValue("right-to-left")
val rightToLeft: LayoutOrientation
}
}
// TypeScript
type LayoutOrientation = 'top_to_bottom'
| 'left_to_right'
| 'bottom_to_top'
| 'right_to_left'
// Kotlin
import seskar.js.Case
sealed external interface LayoutOrientation {
companion object {
@JsValue("top_to_bottom")
val TOP_TO_BOTTOM: LayoutOrientation
@JsValue("left_to_right")
val LEFT_TO_RIGHT: LayoutOrientation
@JsValue("bottom_to_top")
val bottomToTop: LayoutOrientation
@JsValue("right_to_left")
val rightToLeft: LayoutOrientation
}
}
Use String
or Int
constant as union value
// TypeScript
type Align = 't' | 'l' | 'b' | 'r'
// Kotlin
import seskar.js.JsValue
sealed external interface CustomAlign {
companion object {
@JsValue("t")
val TOP: CustomAlign
@JsValue("l")
val LEFT: CustomAlign
@JsValue("b")
val BOTTOM: CustomAlign
@JsValue("r")
val RIGHT: CustomAlign
}
}
println(CustomAlign.TOP) // 't'
println(CustomAlign.LEFT) // 'l'
// TypeScript
type GRAPH_ITEM_TYPE_NODE = 1
type GRAPH_ITEM_TYPE_EDGE = 2
type GRAPH_ITEM_TYPE_PORT = 3
type GRAPH_ITEM_TYPE_LABEL = 4
type GraphItemType = GRAPH_ITEM_TYPE_NODE
| GRAPH_ITEM_TYPE_EDGE
| GRAPH_ITEM_TYPE_PORT
| GRAPH_ITEM_TYPE_LABEL
// Kotlin
import seskar.js.JsRawValue
""
sealed external interface GraphItemType {
companion object {
@JsRawValue("1")
val NODE: GraphItemType
@JsRawValue("2")
val EDGE: GraphItemType
@JsRawValue("4")
val PORT: GraphItemType
@JsRawValue("8")
val LABEL: GraphItemType
}
}
println(GraphItemType.EDGE) // 2
println(GraphItemType.PORT) // 4