Browse Source

CfW: Allow web resource routing configuration (#3852)

This commit changes the default resource routing behaviour:
- It used to search for a file in the root directory (on a domain level)
- After this change, it will search for a file relatively to the current
url segment

Besides that, we add a small configuration to let developers change the
default behaviour when needed.

___

usage examples:

```kotlin
// 1
configureWebResources {
   setResourceFactory { path -> urlResource("/myApp1/resources/$path") }
}

// 2
configureWebResources {
  setResourcelFactory { path -> urlResource("https://mycdn.com/myApp1/res/$path") }
}
 ```

___
This will fix https://github.com/JetBrains/compose-multiplatform/issues/3413 (currently it bothers our users)
pull/3861/head
Oleksandr Karpovich 1 year ago committed by GitHub
parent
commit
73802292ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      components/gradle.properties
  2. 5
      components/resources/demo/shared/build.gradle.kts
  3. 10
      components/resources/demo/shared/src/jsMain/kotlin/main.js.kt
  4. 101
      components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/Resource.js.kt

2
components/gradle.properties

@ -5,7 +5,7 @@ kotlin.code.style=official
# __KOTLIN_COMPOSE_VERSION__
kotlin.version=1.8.22
# __LATEST_COMPOSE_RELEASE_VERSION__
compose.version=1.5.0-dev1112
compose.version=1.5.10-rc01
agp.version=7.3.1
org.jetbrains.compose.experimental.jscanvas.enabled=true
org.jetbrains.compose.experimental.macos.enabled=true

5
components/resources/demo/shared/build.gradle.kts

@ -109,3 +109,8 @@ android {
compose.experimental {
web.application {}
}
// TODO: remove this block after we update on a newer kotlin. Currently there is an error: `error:0308010C:digital envelope routines::unsupported`
rootProject.plugins.withType<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin> {
rootProject.the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension>().nodeVersion = "16.0.0"
}

10
components/resources/demo/shared/src/jsMain/kotlin/main.js.kt

@ -11,10 +11,20 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.configureWebResources
import org.jetbrains.compose.resources.demo.shared.UseResources
import org.jetbrains.compose.resources.urlResource
import org.jetbrains.skiko.wasm.onWasmReady
fun main() {
@OptIn(ExperimentalResourceApi::class)
configureWebResources {
// Not necessary - It's the same as the default. We add it here just to present this feature.
setResourceFactory { urlResource("./$it") }
}
onWasmReady {
Window("Resources demo") {
MainView()

101
components/resources/library/src/jsMain/kotlin/org/jetbrains/compose/resources/Resource.js.kt

@ -5,40 +5,97 @@
package org.jetbrains.compose.resources
import kotlinx.browser.window
import kotlinx.coroutines.await
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.khronos.webgl.ArrayBuffer
import org.khronos.webgl.Int8Array
import org.w3c.dom.parsing.DOMParser
import org.w3c.xhr.ARRAYBUFFER
import org.w3c.xhr.XMLHttpRequest
import org.w3c.xhr.XMLHttpRequestResponseType
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
/**
* Represents the configuration object for web resources.
*
* @see configureWebResources - for overriding the default configuration.
*/
@Suppress("unused")
@ExperimentalResourceApi
object WebResourcesConfiguration {
/**
* An internal default factory method for creating [Resource] from a given path.
* It can be changed at runtime by using [setResourceFactory].
*/
@ExperimentalResourceApi
internal var jsResourceImplFactory: (path: String) -> Resource = { urlResource("./$it") }
/**
* Sets a custom factory for the [resource] function to create [Resource] instances.
* Once set, the [factory] will effectively define the implementation of the [resource] function.
*
* @param factory A lambda that accepts a path and produces a [Resource] instance.
* @see configureWebResources for examples on how to use this function.
*/
@ExperimentalResourceApi
fun setResourceFactory(factory: (path: String) -> Resource) {
jsResourceImplFactory = factory
}
}
/**
* Configures the web resources behavior.
*
* Allows users to override default behavior and provide custom logic for generating [Resource] instances.
*
* @param configure Configuration lambda applied to [WebResourcesConfiguration].
* @see WebResourcesConfiguration For detailed configuration options.
*
* Examples:
* ```
* configureWebResources {
* setResourceFactory { path -> urlResource("/myApp1/resources/$path") }
* }
* configureWebResources {
* setResourceFactory { path -> urlResource("https://mycdn.com/myApp1/res/$path") }
* }
* ```
*/
@Suppress("unused")
@ExperimentalResourceApi
fun configureWebResources(configure: WebResourcesConfiguration.() -> Unit) {
WebResourcesConfiguration.configure()
}
/**
* Generates a [Resource] instance based on the provided [path].
*
* By default, the path is treated as relative to the current URL segment.
* The default behaviour can be overridden by using [configureWebResources].
*
* @param path The path or resource id used to generate the [Resource] instance.
* @return A [Resource] instance corresponding to the provided path.
*/
@ExperimentalResourceApi
actual fun resource(path: String): Resource = WebResourcesConfiguration.jsResourceImplFactory(path)
/**
* Creates a [Resource] instance based on the provided [url].
*
* @param url The URL used to access the [Resource].
* @return A [Resource] instance accessible by the given URL.
*/
@ExperimentalResourceApi
actual fun resource(path: String): Resource = JSResourceImpl(path)
fun urlResource(url: String): Resource = JSUrlResourceImpl(url)
@ExperimentalResourceApi
private class JSResourceImpl(path: String) : AbstractResourceImpl(path) {
private class JSUrlResourceImpl(url: String) : AbstractResourceImpl(url) {
override suspend fun readBytes(): ByteArray {
return suspendCoroutine { continuation ->
val req = XMLHttpRequest()
req.open("GET", "/$path", true)
req.responseType = XMLHttpRequestResponseType.ARRAYBUFFER
req.onload = { event ->
val arrayBuffer = req.response
if (arrayBuffer is ArrayBuffer) {
continuation.resume(arrayBuffer.toByteArray())
} else {
continuation.resumeWithException(MissingResourceException(path))
}
}
req.send(null)
val response = window.fetch(path).await()
if (!response.ok) {
throw MissingResourceException(path)
}
return response.arrayBuffer().await().toByteArray()
}
}

Loading…
Cancel
Save