Skip to content
Open
6 changes: 5 additions & 1 deletion dependencies.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
[versions]
kotlin = "2.0.10"
kotlin = "2.3.0"
kotlinxBrowser = "0.5.0"

coroutines = "1.8.0"
jetbrainsRuntime-api = "1.5.0"

Expand All @@ -22,6 +24,8 @@ coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", ve

jetbrainsRuntime-api = { module = "org.jetbrains.runtime:jbr-api", version.ref = "jetbrainsRuntime-api" }

kotlinx-browser = { module = "org.jetbrains.kotlinx:kotlinx-browser", version.ref = "kotlinxBrowser" }
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a trailing space after the closing brace on this line. Remove the trailing whitespace.

Suggested change
kotlinx-browser = { module = "org.jetbrains.kotlinx:kotlinx-browser", version.ref = "kotlinxBrowser" }
kotlinx-browser = { module = "org.jetbrains.kotlinx:kotlinx-browser", version.ref = "kotlinxBrowser" }

Copilot uses AI. Check for mistakes.

android-gradlePlugin = { module = "com.android.tools.build:gradle", version.ref = "androidGradlePlugin" }
dokka-gradlePlugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
gradleDownloadTask-gradlePlugin = { module = "de.undercouch:gradle-download-task", version.ref = "gradleDownloadTask" }
Expand Down
22 changes: 7 additions & 15 deletions samples/SkiaWebSample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ kotlin {
binaries.executable()
}

wasmJs() {
wasmJs {
browser {
commonWebpackConfig {
outputFileName = "webApp.js"
Expand All @@ -61,24 +61,16 @@ kotlin {
}

sourceSets {
val commonMain by getting {
commonMain.dependencies {
implementation(libs.skiko)
}

webMain {
dependencies {
implementation(libs.skiko)
implementation(libs.browser)
}
}

val webMain by creating {
dependsOn(commonMain)
resources.setSrcDirs(resources.srcDirs)
resources.srcDirs(unpackWasmRuntime.map { it.destinationDir })
}

val jsMain by getting {
dependsOn(webMain)
}

val wasmJsMain by getting {
dependsOn(webMain)
}
}
}
2 changes: 1 addition & 1 deletion samples/SkiaWebSample/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
kotlin.code.style=official
kotlin.version=2.0.10
kotlin.version=2.3.0
skiko.version=0.9.24
#skiko.composite.build=1
3 changes: 3 additions & 0 deletions samples/SkiaWebSample/settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ dependencyResolutionManagement {
versionCatalogs {
create("libs") {
version("skiko", providers.gradleProperty("skiko.version").get())
version("kotlinxBrowser", "0.5.0")

library("skiko", "org.jetbrains.skiko", "skiko").versionRef("skiko")
library("skiko-wasm-runtime", "org.jetbrains.skiko", "skiko-js-wasm-runtime").versionRef("skiko")
library("browser", "org.jetbrains.kotlinx", "kotlinx-browser").versionRef("kotlinxBrowser")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ fun main() {
onWasmReady {
val description = "Skiko running with ${getPlatform().name}"
document.title = description
findElementById("description")?.innerHTML = description
document.getElementById("description")?.innerHTML = description
runApp()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fun main() {
onWasmReady {
val description = "Skiko running with ${getPlatform().name}"
document.title = description
findElementById("description")?.innerHTML = description
document.getElementById("description")?.innerHTML = description
runApp()
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package org.jetbrains.skiko.sample.js

import kotlinx.browser.document
import org.jetbrains.skia.Canvas
import org.jetbrains.skia.Paint
import org.jetbrains.skia.Rect
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaLayerRenderDelegate
import org.jetbrains.skiko.SkikoRenderDelegate
import org.w3c.dom.Element
import kotlinx.browser.document

private class DemoApp: SkikoRenderDelegate {
private val paint = Paint()
Expand All @@ -25,7 +24,7 @@ private class DemoApp: SkikoRenderDelegate {
internal fun runApp() {
for (index in 1 .. 3) {
val skiaLayer = SkiaLayer()
val canvas = findElementById("c$index")!!
val canvas = document.getElementById("c$index")!!
val app = if (index == 3) {
DemoApp()
} else {
Expand All @@ -35,6 +34,4 @@ internal fun runApp() {
skiaLayer.attachTo(canvas)
skiaLayer.needRedraw()
}
}

fun findElementById(id: String): Element? = document.getElementById(id)
}
14 changes: 9 additions & 5 deletions skiko/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@file:OptIn(ExperimentalKotlinGradlePluginApi::class)
@file:OptIn(ExperimentalKotlinGradlePluginApi::class, ExperimentalWasmDsl::class)

import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.LibraryPlugin
Expand All @@ -8,9 +8,9 @@ import org.jetbrains.compose.internal.publishing.MavenCentralProperties
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import tasks.configuration.*

plugins {
Expand Down Expand Up @@ -96,7 +96,7 @@ kotlin {
skikoProjectContext.declareWasmTasks()

js {
moduleName = "skiko-kjs" // override the name to avoid name collision with a different skiko.js file
outputModuleName.set("skiko-kjs") // override the name to avoid name collision with a different skiko.js file
browser {
testTask {
useKarma {
Expand All @@ -120,7 +120,7 @@ kotlin {

@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = "skiko-kjs-wasm" // override the name to avoid name collision with a different skiko.js file
outputModuleName.set("skiko-kjs-wasm") // override the name to avoid name collision with a different skiko.js file
browser {
testTask {
useKarma {
Expand Down Expand Up @@ -183,6 +183,10 @@ kotlin {
implementation(libs.coroutines.core.jvm)
}

skikoProjectContext.webMainSourceSet?.dependencies {
implementation(libs.kotlinx.browser)
}

skikoProjectContext.awtMainSourceSet?.dependencies {
implementation(libs.jetbrainsRuntime.api)
}
Expand Down Expand Up @@ -392,7 +396,7 @@ tasks.register("printSkiaVersion") {

tasks.withType<KotlinNativeCompile>().configureEach {
// https://youtrack.jetbrains.com/issue/KT-56583
compilerOptions.freeCompilerArgs.add("-XXLanguage:+ImplicitSignedToUnsignedIntegerConversion")
//compilerOptions.freeCompilerArgs.add("-XXLanguage:+ImplicitSignedToUnsignedIntegerConversion")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this line commented intentionally?

https://youtrack.jetbrains.com/issue/KT-56583 is fixed, can the line be removed completely?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this line was commented intentionally - I just wanted to discuss with our fellow composers whether it's needed but my understanding that it is indeed no longer needed so I'll just remove it

compilerOptions.freeCompilerArgs.add("-opt-in=kotlinx.cinterop.ExperimentalForeignApi")
}

Expand Down
2 changes: 1 addition & 1 deletion skiko/buildSrc/gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
kotlin.version=2.0.10
kotlin.version=2.3.0
9 changes: 1 addition & 8 deletions skiko/buildSrc/src/main/kotlin/imports.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@ import org.gradle.api.Project
val Project.wasmImports
get() = layout.buildDirectory.dir("imports").get().asFile

val Project.setupMjs
get() = wasmImports.resolve("setup.mjs")

val Project.setupReexportMjs
get() = wasmImports.resolve("js-reexport-symbols.mjs")

val Project.skikoTestMjs
get() = wasmImports.resolve("skiko-test.mjs")
fun Project.wasmImport(name: String) = wasmImports.resolve(name)

const val IMPORT_GENERATOR = "import-generator"
2 changes: 2 additions & 0 deletions skiko/buildSrc/src/main/kotlin/sourceHierarchy.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ val SkikoProjectContext.awtTestSourceSet get() = if (project.supportAwt) kotlin.

val SkikoProjectContext.androidMainSourceSet get() = if (project.supportAndroid) kotlin.sourceSets.getByName("androidMain") else null

val SkikoProjectContext.webMainSourceSet get() = if (project.supportWeb) kotlin.sourceSets.getByName("webMain") else null

val SkikoProjectContext.webTestSourceSet get() = if (project.supportWeb) kotlin.sourceSets.getByName("webTest") else null

val SkikoProjectContext.wasmJsTest get() = if (project.supportWeb) kotlin.sourceSets.getByName("wasmJsTest") else null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ fun configureCinterop(
}
target.compilations.getByName("main") {
cinterops.create(cinteropName).apply {
defFileProperty.set(writeCInteropDef.map { it.outputFile.get().asFile })
definitionFile.set(writeCInteropDef.flatMap { it.outputFile })
}
}
}
Expand Down Expand Up @@ -294,9 +294,13 @@ fun SkikoProjectContext.configureNativeTarget(os: OS, arch: Arch, target: Kotlin
target.binaries.all {
freeCompilerArgs += allLibraries.map { listOf("-include-binary", it) }.flatten() + linkerFlags
}


target.compilations.all {
kotlinOptions {
freeCompilerArgs += allLibraries.map { listOf("-include-binary", it) }.flatten() + linkerFlags
compilerOptions.configure {
freeCompilerArgs.addAll(
allLibraries.flatMap { listOf("-include-binary", it) } + linkerFlags
)
}
}

Expand Down Expand Up @@ -343,7 +347,7 @@ fun KotlinMultiplatformExtension.configureIOSTestsWithMetal(project: Project) {
if (targets.names.contains(target)) {
val testBinary = targets.getByName<KotlinNativeTarget>(target).binaries.getTest("DEBUG")
project.tasks.register(target + "TestWithMetal") {
dependsOn(testBinary.linkTask)
dependsOn(testBinary.linkTaskProvider)
doLast {
val simulatorIdPropertyKey = "skiko.iosSimulatorUUID"
val simulatorId = project.findProperty(simulatorIdPropertyKey)?.toString()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
@file:OptIn(ExperimentalWasmDsl::class)

package tasks.configuration

import Arch
Expand All @@ -19,16 +17,22 @@ import org.gradle.kotlin.dsl.getting
import org.gradle.kotlin.dsl.provideDelegate
import org.gradle.kotlin.dsl.registering
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsTargetDsl
import projectDirs
import registerOrGetSkiaDirProvider
import setupMjs
import setupReexportMjs
import skikoTestMjs
import supportWeb
import wasmImport
import java.io.File

private val Project.setupMjs
get() = wasmImport("setup.mjs")

private val Project.setupReexportMjs
get() = wasmImport("js-reexport-symbols.mjs")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this is not needed anymore?

Judging based on the below changes where this is removed:

if (reexportPath == null) {
                    writer.appendLine("window['${symbolName}'] = (...a) => loadedWasm._[\"${symbolName}\"](...a)")
                }


private val Project.skikoTestMjs
get() = wasmImport("skiko-test.mjs")

fun SkikoProjectContext.declareWasmTasks() {
if (!project.supportWeb) {
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ internal class ImportGeneratorExtension(

importGenerator.getExportSymbols().forEach { symbolName ->
writer.appendLine("export let ${symbolName} = (...a) => ($symbolName = loadedWasm._[\"${symbolName}\"])(...a)")
if (reexportPath == null) {
writer.appendLine("window['${symbolName}'] = (...a) => loadedWasm._[\"${symbolName}\"](...a)")
}
}
}

Expand All @@ -40,7 +37,7 @@ internal class ImportGeneratorExtension(
reexportFile.writer().use { reexportWriter ->
reexportWriter.appendLine("import * as wasmApi from \"./skiko.mjs\";")
reexportWriter.appendLine("window['GL'] = wasmApi.GL;")
reexportWriter.appendLine("export const api = { awaitSkiko: wasmApi.awaitSkiko }")
reexportWriter.appendLine("export const awaitSkiko = wasmApi.awaitSkiko")

importGenerator.getExportSymbols().forEach { symbolName ->
reexportWriter.appendLine("window['${symbolName}'] = wasmApi['${symbolName}'];")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
@OptIn(ExperimentalCompilerApi::class)
class ImportGeneratorRegistrar : CompilerPluginRegistrar() {
override val supportsK2: Boolean = true
override val pluginId: String = "skiko-import-generator"

override fun ExtensionStorage.registerExtensions(
configuration: CompilerConfiguration,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package org.jetbrains.skiko

import org.jetbrains.kotlin.DeprecatedForRemovalCompilerApi
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.fromSymbolOwner
import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.defaultType
Expand All @@ -25,12 +27,12 @@ internal class ImportGeneratorTransformer(private val pluginContext: IrPluginCon

@Suppress("UNCHECKED_CAST")
private fun IrConstructorCall.getStringValue(value: String): String =
(getValueArgument(Name.identifier(value)) as IrConst<String>).value
(getValueArgument(Name.identifier(value)) as IrConst).value as String

@OptIn(UnsafeDuringIrConstructionAPI::class)
@OptIn(UnsafeDuringIrConstructionAPI::class, DeprecatedForRemovalCompilerApi::class)
private fun IrFunction.addWasmImportAnnotation(name: String) {
val annotationClass = pluginContext.referenceClass(
ClassId.fromString("kotlin/wasm/WasmImport") // Replace with your fully qualified annotation name
ClassId.fromString("kotlin/wasm/WasmImport")
) ?: return

val ctor = annotationClass.owner.constructors.first()
Expand Down Expand Up @@ -61,14 +63,40 @@ internal class ImportGeneratorTransformer(private val pluginContext: IrPluginCon
annotations += annotationCall
}

@OptIn(UnsafeDuringIrConstructionAPI::class, DeprecatedForRemovalCompilerApi::class)
private fun IrFunction.addJsNameAnnotation(name: String) {
val annotationClass = pluginContext.referenceClass(
ClassId.fromString("kotlin/js/JsName")
) ?: return

val ctor = annotationClass.owner.constructors.first()

val annotationCall = IrConstructorCallImpl.fromSymbolOwner(
startOffset = startOffset,
endOffset = endOffset,
type = annotationClass.owner.defaultType,
constructorSymbol = ctor.symbol,
)

annotationCall.putValueArgument(0, IrConstImpl.string(
startOffset,
endOffset,
pluginContext.irBuiltIns.stringType,
name
))

annotations += annotationCall
}

override fun visitFunction(declaration: IrFunction): IrStatement {
return super.visitFunction(declaration).apply {
if (this !is IrFunction) return@apply

val jsNameAnnotation = getAnnotation(FqName("kotlin.js.JsName"))
val webImportAnnotation = getAnnotation(FqName("org.jetbrains.skiko.WebImport"))
?: return@apply

val name = jsNameAnnotation.getStringValue("name")
val name = webImportAnnotation.getStringValue("name")
addJsNameAnnotation(name)
addWasmImportAnnotation(name)

exportSymbols.add(name)
Expand Down
Loading
Loading