Seskar
Kotlin/JS sugar
Install / Use
/learn @turansky/SeskarREADME
Seskar
Seskar is a Gradle plugin that provides useful additions for Kotlin/JS projects.
Setup
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.3.20"
id("io.github.turansky.seskar") version "4.40.0"
}
Kotlin/JS requirements
Lazy functions
Puts a function in a separate JS chunk. The chunk will be loaded when the function is called the first time.
Setup
Add the dependency kotlin-js of Kotlin Wrappers to your project.
// root settings.gradle.kts
dependencyResolutionManagement {
repositories {
mavenCentral()
}
versionCatalogs {
create("kotlinWrappers") {
val wrappersVersion = "%LAST_WRAPPERS_VERSION%"
from("org.jetbrains.kotlin-wrappers:kotlin-wrappers-catalog:$wrappersVersion")
}
}
}
// build.gradle.kts
sourceSets {
jsMain {
dependencies {
implementation(kotlinWrappers.js)
}
}
}
Usage
// 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
import js.lazy.Lazy
import js.lazy.LazyFunction
/**
* Function will be located in separate JS chunk
*/
@Lazy
val createCalculationWithHeavyLibrary = LazyFunction<Int> {
val calculator = HeavyCalculator()
calculator.calculate()
}
React
Lazy components
// Content.kt
@Lazy
val Content = FC {
MyHeavyComponent1()
MyHeavyComponent2()
}
// App.kt
val App = FC {
Header()
Suspense {
Content()
}
Footer()
}
Examples
Conditional rendering
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"
}
Dependencies
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.
Example
value class Count(
private val value: Int,
)
val Counter = FC {
val count: Count = useCount()
useEffect(count) {
println("Count changed: $count")
}
}
Without plugin
function Counter() {
var count = useCount()
useEffect(
() => {
println(`Count changed: $count`)
},
// AUTOBOXING
[new Count(count)],
)
}
With plugin
function Counter() {
var count = useCount()
useEffect(
() => {
println(`Count changed: $count`)
},
// NO AUTOBOXING
[count],
)
}
Unions
AS-IS
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'
Kebab case
// 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
}
}
Snake case
// 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
}
}
Custom
Use String or Int constant as union value
String
// 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'
Int
// 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
Related Skills
node-connect
328.6kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
80.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
328.6kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
80.9kCommit, push, and open a PR
