Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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=0.0.0-dev1332
eymar marked this conversation as resolved.
Show resolved Hide resolved
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>
36 changes: 31 additions & 5 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,21 @@ kotlin {
dependsOn(skikoTest)
dependsOn(blockingTest)
}
val jsMain by getting {
val webMain by creating {
terrakok marked this conversation as resolved.
Show resolved Hide resolved
dependsOn(skikoMain)
}
val jsMain by getting {
dependsOn(webMain)
}
val wasmJsMain by getting {
dependsOn(webMain)
}
val jsTest by getting {
dependsOn(skikoTest)
}
val wasmJsTest by getting {
dependsOn(skikoTest)
}
}
}

Expand Down Expand Up @@ -169,3 +188,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
terrakok marked this conversation as resolved.
Show resolved Hide resolved
}
}
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()
terrakok marked this conversation as resolved.
Show resolved Hide resolved
}