Browse Source

Add K/Wasm target to components/resources library (#4028)

Changes:
- added k/wasm target to library and demo
- added libs.versions.toml with coroutines version


Tested:
- using demo project
- publishToMavenLocal

I'll setup the test separately.

---------

Co-authored-by: Oleksandr.Karpovich <oleksandr.karpovich@jetbrains.com>
pull/4031/head
Oleksandr Karpovich 5 months ago committed by GitHub
parent
commit
5e999e7b7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      components/gradle.properties
  2. 7
      components/gradle/libs.versions.toml
  3. 6
      components/resources/demo/shared/build.gradle.kts
  4. 16
      components/resources/demo/shared/src/wasmJsMain/kotlin/main.wasm.kt
  5. 11
      components/resources/demo/shared/src/wasmJsMain/resources/index.html
  6. 41
      components/resources/library/build.gradle.kts
  7. 13
      components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ImageResources.wasmJs.kt
  8. 51
      components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/ResourceReader.wasmJs.kt
  9. 19
      components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/vector/xmldom/ElementImpl.kt
  10. 31
      components/resources/library/src/wasmJsMain/kotlin/org/jetbrains/compose/resources/vector/xmldom/NodeImpl.kt
  11. 7
      components/resources/library/src/wasmJsTest/kotlin/org/jetbrains/compose/resources/TestUtils.wasmJs.kt
  12. 0
      components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/PlatformState.web.kt
  13. 0
      components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/Resource.web.kt

4
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

7
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" }

6
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(),

16
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()
}
}

11
components/resources/demo/shared/src/wasmJsMain/resources/index.html

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Resources demo + K/Wasm</title>
</head>
<body>
<canvas id="ComposeTarget"></canvas>
<script src="shared.js"></script>
</body>
</html>

41
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
}
}

13
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())
}

51
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<Response>()
if (!response.ok) {
throw MissingResourceException(resPath)
}
return response.arrayBuffer().await<ArrayBuffer>().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() }
}
}

19
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) ?: ""
}

31
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) ?: ""
}

7
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")
}

0
components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/PlatformState.js.kt → components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/PlatformState.web.kt

0
components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/Resource.js.kt → components/resources/library/src/webMain/kotlin/org/jetbrains/compose/resources/Resource.web.kt

Loading…
Cancel
Save