Skip to content

Commit

Permalink
Add K/Wasm target to components/resources library (#4028)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
eymar and Oleksandr.Karpovich committed Dec 12, 2023
1 parent 972c740 commit 5e999e7
Show file tree
Hide file tree
Showing 13 changed files with 198 additions and 8 deletions.
4 changes: 2 additions & 2 deletions components/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions components/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -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 changes: 6 additions & 0 deletions components/resources/demo/shared/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl

plugins {
kotlin("multiplatform")
Expand Down Expand Up @@ -35,6 +36,11 @@ kotlin {
}
binaries.executable()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser()
binaries.executable()
}

listOf(
macosX64(),
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
@@ -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 changes: 35 additions & 6 deletions components/resources/library/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl

plugins {
kotlin("multiplatform")
Expand Down Expand Up @@ -31,6 +32,15 @@ kotlin {
})
}
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser {
testTask(Action {
// TODO: fix the test setup and enable
enabled = false
})
}
}
macosX64()
macosArm64()

Expand All @@ -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"))
}
}
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
}
}
}

Expand Down Expand Up @@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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())
}
Original file line number Diff line number Diff line change
@@ -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() }
}
}
Original file line number Diff line number Diff line change
@@ -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) ?: ""
}
Original file line number Diff line number Diff line change
@@ -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) ?: ""

}
Original file line number Diff line number Diff line change
@@ -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 comments on commit 5e999e7

Please sign in to comment.