diff --git a/components/gradle.properties b/components/gradle.properties
index 1555ac6273f..f4fd2d49f3c 100644
--- a/components/gradle.properties
+++ b/components/gradle.properties
@@ -8,13 +8,13 @@ android.useAndroidX=true
#Versions
kotlin.version=1.9.21
-compose.version=1.6.0-dev1323
+compose.version=1.6.0-dev1334
agp.version=8.1.2
#Compose
org.jetbrains.compose.experimental.jscanvas.enabled=true
+org.jetbrains.compose.experimental.wasm.enabled=true
org.jetbrains.compose.experimental.macos.enabled=true
-org.jetbrains.compose.experimental.uikit.enabled=true
compose.resources.always.generate.accessors=true
compose.desktop.verbose=true
compose.useMavenLocal=false
diff --git a/components/gradle/libs.versions.toml b/components/gradle/libs.versions.toml
new file mode 100644
index 00000000000..8eaf2cf1577
--- /dev/null
+++ b/components/gradle/libs.versions.toml
@@ -0,0 +1,7 @@
+[versions]
+kotlinx-coroutines = "1.8.0-RC"
+
+[libraries]
+kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
+kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
+kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
\ No newline at end of file
diff --git a/components/resources/demo/shared/build.gradle.kts b/components/resources/demo/shared/build.gradle.kts
index 521e303e385..af97e41f330 100644
--- a/components/resources/demo/shared/build.gradle.kts
+++ b/components/resources/demo/shared/build.gradle.kts
@@ -1,4 +1,5 @@
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
+import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
plugins {
kotlin("multiplatform")
@@ -35,6 +36,11 @@ kotlin {
}
binaries.executable()
}
+ @OptIn(ExperimentalWasmDsl::class)
+ wasmJs {
+ browser()
+ binaries.executable()
+ }
listOf(
macosX64(),
diff --git a/components/resources/demo/shared/src/wasmJsMain/kotlin/main.wasm.kt b/components/resources/demo/shared/src/wasmJsMain/kotlin/main.wasm.kt
new file mode 100644
index 00000000000..bcd51ccb26d
--- /dev/null
+++ b/components/resources/demo/shared/src/wasmJsMain/kotlin/main.wasm.kt
@@ -0,0 +1,16 @@
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.window.CanvasBasedWindow
+import org.jetbrains.compose.resources.ExperimentalResourceApi
+import org.jetbrains.compose.resources.configureWebResources
+import org.jetbrains.compose.resources.demo.shared.UseResources
+
+@OptIn(ExperimentalComposeUiApi::class, ExperimentalResourceApi::class)
+fun main() {
+ configureWebResources {
+ // Not necessary - It's the same as the default. We add it here just to present this feature.
+ resourcePathMapping { path -> "./$path" }
+ }
+ CanvasBasedWindow("Resources demo + K/Wasm") {
+ UseResources()
+ }
+}
\ No newline at end of file
diff --git a/components/resources/demo/shared/src/wasmJsMain/resources/index.html b/components/resources/demo/shared/src/wasmJsMain/resources/index.html
new file mode 100644
index 00000000000..27b33ced679
--- /dev/null
+++ b/components/resources/demo/shared/src/wasmJsMain/resources/index.html
@@ -0,0 +1,11 @@
+
+
+
+
+ Resources demo + K/Wasm
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/resources/library/build.gradle.kts b/components/resources/library/build.gradle.kts
index 8fa273110e2..ba8ef3cfb9c 100644
--- a/components/resources/library/build.gradle.kts
+++ b/components/resources/library/build.gradle.kts
@@ -1,4 +1,5 @@
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
+import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
plugins {
kotlin("multiplatform")
@@ -31,6 +32,15 @@ kotlin {
})
}
}
+ @OptIn(ExperimentalWasmDsl::class)
+ wasmJs {
+ browser {
+ testTask(Action {
+ // TODO: fix the test setup and enable
+ enabled = false
+ })
+ }
+ }
macosX64()
macosArm64()
@@ -50,18 +60,18 @@ kotlin {
// ┌───┴───┬──│────────┐ │
// │ native │ jvmAndAndroid
// │ ┌───┴───┐ │ ┌───┴───┐
- // js ios macos desktop android
+ // web ios macos desktop android
val commonMain by getting {
dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
+ implementation(libs.kotlinx.coroutines.core)
}
}
val commonTest by getting {
dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
+ implementation(libs.kotlinx.coroutines.test)
implementation(kotlin("test"))
}
}
@@ -96,7 +106,7 @@ kotlin {
dependencies {
implementation(compose.desktop.currentOs)
implementation("org.jetbrains.compose.ui:ui-test-junit4:$composeVersion")
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.7.3")
+ implementation(libs.kotlinx.coroutines.swing)
}
}
val androidMain by getting {
@@ -122,12 +132,24 @@ kotlin {
dependsOn(skikoTest)
dependsOn(blockingTest)
}
- val jsMain by getting {
+ val webMain by creating {
dependsOn(skikoMain)
}
- val jsTest by getting {
+ val jsMain by getting {
+ dependsOn(webMain)
+ }
+ val wasmJsMain by getting {
+ dependsOn(webMain)
+ }
+ val webTest by creating {
dependsOn(skikoTest)
}
+ val jsTest by getting {
+ dependsOn(webTest)
+ }
+ val wasmJsTest by getting {
+ dependsOn(webTest)
+ }
}
}
@@ -169,3 +191,10 @@ configureMavenPublication(
artifactId = "components-resources",
name = "Resources for Compose JB"
)
+
+afterEvaluate {
+ // TODO(o.k.): remove this after we refactor jsAndWasmMain source set in skiko to get rid of broken "common" js-interop
+ tasks.configureEach {
+ if (name == "compileWebMainKotlinMetadata") enabled = false
+ }
+}
\ No newline at end of file
diff --git a/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ImageResources.wasmJs.kt b/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ImageResources.wasmJs.kt
new file mode 100644
index 00000000000..4de24550e75
--- /dev/null
+++ b/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ImageResources.wasmJs.kt
@@ -0,0 +1,13 @@
+package org.jetbrains.compose.resources
+
+import org.jetbrains.compose.resources.vector.xmldom.Element
+import org.jetbrains.compose.resources.vector.xmldom.ElementImpl
+import org.jetbrains.compose.resources.vector.xmldom.MalformedXMLException
+import org.w3c.dom.parsing.DOMParser
+
+internal actual fun ByteArray.toXmlElement(): Element {
+ val xmlString = decodeToString()
+ val xmlDom = DOMParser().parseFromString(xmlString, "application/xml".toJsString())
+ val domElement = xmlDom.documentElement ?: throw MalformedXMLException("missing documentElement")
+ return ElementImpl(domElement.unsafeCast())
+}
\ No newline at end of file
diff --git a/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.wasmJs.kt b/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.wasmJs.kt
new file mode 100644
index 00000000000..72513cc9834
--- /dev/null
+++ b/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.wasmJs.kt
@@ -0,0 +1,51 @@
+package org.jetbrains.compose.resources
+
+import kotlinx.browser.window
+import kotlinx.coroutines.await
+import org.khronos.webgl.ArrayBuffer
+import org.khronos.webgl.Int8Array
+import org.w3c.fetch.Response
+import kotlin.wasm.unsafe.UnsafeWasmMemoryApi
+import kotlin.wasm.unsafe.withScopedMemoryAllocator
+
+/**
+ * Reads the content of the resource file at the specified path and returns it as a byte array.
+ *
+ * @param path The path of the file to read in the resource's directory.
+ * @return The content of the file as a byte array.
+ */
+@ExperimentalResourceApi
+actual suspend fun readResourceBytes(path: String): ByteArray {
+ val resPath = WebResourcesConfiguration.getResourcePath(path)
+ val response = window.fetch(resPath).await()
+ if (!response.ok) {
+ throw MissingResourceException(resPath)
+ }
+ return response.arrayBuffer().await().toByteArray()
+}
+
+private fun ArrayBuffer.toByteArray(): ByteArray {
+ val source = Int8Array(this, 0, byteLength)
+ return jsInt8ArrayToKotlinByteArray(source)
+}
+
+@JsFun(
+ """ (src, size, dstAddr) => {
+ const mem8 = new Int8Array(wasmExports.memory.buffer, dstAddr, size);
+ mem8.set(src);
+ }
+"""
+)
+internal external fun jsExportInt8ArrayToWasm(src: Int8Array, size: Int, dstAddr: Int)
+
+internal fun jsInt8ArrayToKotlinByteArray(x: Int8Array): ByteArray {
+ val size = x.length
+
+ @OptIn(UnsafeWasmMemoryApi::class)
+ return withScopedMemoryAllocator { allocator ->
+ val memBuffer = allocator.allocate(size)
+ val dstAddress = memBuffer.address.toInt()
+ jsExportInt8ArrayToWasm(x, size, dstAddress)
+ ByteArray(size) { i -> (memBuffer + i).loadByte() }
+ }
+}
\ No newline at end of file
diff --git a/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/vector/xmldom/ElementImpl.kt b/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/vector/xmldom/ElementImpl.kt
new file mode 100644
index 00000000000..8a9b566a186
--- /dev/null
+++ b/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/vector/xmldom/ElementImpl.kt
@@ -0,0 +1,19 @@
+package org.jetbrains.compose.resources.vector.xmldom
+
+import org.w3c.dom.Element as DomElement
+
+internal class ElementImpl(val element: DomElement): NodeImpl(element), Element {
+ override val textContent: String?
+ get() = element.textContent
+
+ override val localName: String
+ get() = element.localName
+
+ override val namespaceURI: String
+ get() = element.namespaceURI ?: ""
+
+ override fun getAttributeNS(nameSpaceURI: String, localName: String): String =
+ element.getAttributeNS(nameSpaceURI, localName) ?: ""
+
+ override fun getAttribute(name: String): String = element.getAttribute(name) ?: ""
+}
diff --git a/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/vector/xmldom/NodeImpl.kt b/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/vector/xmldom/NodeImpl.kt
new file mode 100644
index 00000000000..09a0f44200a
--- /dev/null
+++ b/components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/vector/xmldom/NodeImpl.kt
@@ -0,0 +1,31 @@
+package org.jetbrains.compose.resources.vector.xmldom
+
+import org.w3c.dom.Element as DomElement
+import org.w3c.dom.Node as DomNode
+
+internal open class NodeImpl(val n: DomNode): Node {
+ override val textContent: String?
+ get() = n.textContent
+
+ override val nodeName: String
+ get() = n.nodeName
+
+ override val localName = "" /* localName is not a Node property, only applies to Elements and Attrs */
+
+ override val namespaceURI = "" /* namespaceURI is not a Node property, only applies to Elements and Attrs */
+
+ override val childNodes: NodeList by lazy {
+ object: NodeList {
+ override fun item(i: Int): Node {
+ val child = n.childNodes.item(i)
+ ?: throw IndexOutOfBoundsException("no child node accessible at index=$i")
+ return if (child is DomElement) ElementImpl(child) else NodeImpl(child)
+ }
+
+ override val length: Int = n.childNodes.length
+ }
+ }
+
+ override fun lookupPrefix(namespaceURI: String): String = n.lookupPrefix(namespaceURI) ?: ""
+
+}
diff --git a/components/resources/library/src/wasmJsTest/kotlin/org/jetbrains/compose/resources/TestUtils.wasmJs.kt b/components/resources/library/src/wasmJsTest/kotlin/org/jetbrains/compose/resources/TestUtils.wasmJs.kt
new file mode 100644
index 00000000000..52a1bcaab2a
--- /dev/null
+++ b/components/resources/library/src/wasmJsTest/kotlin/org/jetbrains/compose/resources/TestUtils.wasmJs.kt
@@ -0,0 +1,7 @@
+package org.jetbrains.compose.resources
+
+import kotlinx.coroutines.CoroutineScope
+
+actual fun runBlockingTest(block: suspend CoroutineScope.() -> Unit) {
+ TODO("To be implemented in PR 4031")
+}
\ No newline at end of file
diff --git a/components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/PlatformState.js.kt b/components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/PlatformState.web.kt
similarity index 100%
rename from components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/PlatformState.js.kt
rename to components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/PlatformState.web.kt
diff --git a/components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/Resource.js.kt b/components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/Resource.web.kt
similarity index 100%
rename from components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/Resource.js.kt
rename to components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/Resource.web.kt