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 WASM support #1245

Merged
merged 13 commits into from
Mar 26, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Changelog
**Unreleased**
--------------

- **New**: Add WASM targets.

0.20.0
------

Expand Down
34 changes: 27 additions & 7 deletions backstack/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (C) 2024 Slack Technologies, LLC
// SPDX-License-Identifier: Apache-2.0
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask

// Copyright (C) 2022 Slack Technologies, LLC
// SPDX-License-Identifier: Apache-2.0
Expand All @@ -24,12 +25,23 @@ kotlin {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
if (hasProperty("enableWasm")) {
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser {
testTask {
useKarma {
useChromeHeadless()
useConfigDirectory(
rootProject.projectDir
.resolve("internal-test-utils")
.resolve("karma.config.d")
.resolve("wasm")
)
}
}
}
binaries.executable()
}
// endregion

Expand Down Expand Up @@ -70,13 +82,21 @@ kotlin {
}
}
val jvmTest by getting { dependsOn(commonJvmTest) }
// We use a common folder instead of a common source set because there is no commonizer
// which exposes the browser APIs across these two targets.
jsMain { kotlin.srcDir("src/browserMain/kotlin") }
val wasmJsMain by getting { kotlin.srcDir("src/browserMain/kotlin") }
}
}

tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>().configureEach {
// adding it here to make sure skiko is unpacked and available in web tests
// https://github.com/JetBrains/compose-multiplatform/issues/4133
compose.experimental { web.application {} }

tasks.withType<KotlinCompilationTask<*>>().configureEach {
compilerOptions {
// Need to disable, due to 'duplicate library name' warning
// https://youtrack.jetbrains.com/issue/KT-51110
// https://youtrack.jetbrains.com/issue/KT-64115
allWarningsAsErrors = false
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,24 +55,28 @@ public object SaveableStateRegistryBackStackRecordLocalProvider :

@Composable
override fun provideValues(): ImmutableList<ProvidedValue<*>> {
remember {
object : RememberObserver {
override fun onForgotten() {
childRegistry.saveForContentLeavingComposition()
}

override fun onRemembered() {}

override fun onAbandoned() {}
}
}
remember { RememberObserverImpl(childRegistry) }
return list
}
}
}
}
}

// Extracted to work around a WASM bug
// https://youtrack.jetbrains.com/issue/KT-66465#focus=Comments-27-9568825.0-0
private class RememberObserverImpl(
private val childRegistry: BackStackRecordLocalSaveableStateRegistry
) : RememberObserver {
override fun onForgotten() {
childRegistry.saveForContentLeavingComposition()
}

override fun onRemembered() {}

override fun onAbandoned() {}
}

private class BackStackRecordLocalSaveableStateRegistry(
// Note: restored is snapshot-backed because consumeRestored runs in composition
// and must be rolled back if composition does not commit
Expand Down Expand Up @@ -112,7 +116,7 @@ private class BackStackRecordLocalSaveableStateRegistry(
synchronized(lock) {
val list = valueProviders.remove(key)
list?.remove(valueProvider)
if (list != null && list.isNotEmpty()) {
if (!list.isNullOrEmpty()) {
// if there are other providers for this key return list
// back to the map
valueProviders[key] = list
Expand Down
15 changes: 7 additions & 8 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ import org.jetbrains.kotlin.gradle.plugin.AbstractKotlinMultiplatformPluginWrapp
import org.jetbrains.kotlin.gradle.plugin.KotlinBasePlugin
import org.jetbrains.kotlin.gradle.plugin.NATIVE_COMPILER_PLUGIN_CLASSPATH_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.plugin.PLUGIN_CLASSPATH_CONFIGURATION_NAME
import org.jetbrains.kotlin.gradle.targets.js.ir.DefaultIncrementalSyncTask
import org.jetbrains.kotlin.gradle.targets.js.testing.KotlinJsTest
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
import wtf.emulator.EwExtension

buildscript { dependencies { classpath(platform(libs.kotlin.plugins.bom)) } }
Expand Down Expand Up @@ -457,13 +458,11 @@ subprojects {
}
}
}
tasks.withType<KotlinNativeCompile>().configureEach {
notCompatibleWithConfigurationCache("https://youtrack.jetbrains.com/issue/KT-49933")
}
@Suppress("INVISIBLE_REFERENCE")
tasks.withType<org.jetbrains.kotlin.gradle.plugin.mpp.apple.FrameworkCopy>().configureEach {
@Suppress("INVISIBLE_MEMBER")
notCompatibleWithConfigurationCache("https://youtrack.jetbrains.com/issue/KT-49933")

// Workaround for missing task dependency in WASM
val executableCompileSyncTasks = tasks.withType(DefaultIncrementalSyncTask::class.java)
tasks.withType(KotlinJsTest::class.java).configureEach {
mustRunAfter(executableCompileSyncTasks)
}
}

Expand Down
14 changes: 8 additions & 6 deletions circuit-foundation/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@ kotlin {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
if (hasProperty("enableWasm")) {
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
// endregion

Expand Down Expand Up @@ -101,6 +99,10 @@ kotlin {
implementation(libs.compose.ui.testing.junit)
}
}
// We use a common folder instead of a common source set because there is no commonizer
// which exposes the browser APIs across these two targets.
jsMain { kotlin.srcDir("src/browserMain/kotlin") }
val wasmJsMain by getting { kotlin.srcDir("src/browserMain/kotlin") }
}
}

Expand Down
10 changes: 4 additions & 6 deletions circuit-overlay/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@ kotlin {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
if (hasProperty("enableWasm")) {
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
// endregion

Expand Down
14 changes: 8 additions & 6 deletions circuit-retained/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@ kotlin {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
if (hasProperty("enableWasm")) {
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
// endregion

Expand Down Expand Up @@ -80,6 +78,10 @@ kotlin {
implementation(projects.circuitRetained)
}
}
// We use a common folder instead of a common source set because there is no commonizer
// which exposes the browser APIs across these two targets.
jsMain { kotlin.srcDir("src/browserMain/kotlin") }
val wasmJsMain by getting { kotlin.srcDir("src/browserMain/kotlin") }
}

targets.configureEach {
Expand Down
10 changes: 4 additions & 6 deletions circuit-runtime-presenter/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@ kotlin {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
if (hasProperty("enableWasm")) {
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
// endregion

Expand Down
18 changes: 11 additions & 7 deletions circuit-runtime-screen/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,22 @@ kotlin {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
if (hasProperty("enableWasm")) {
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
// endregion

applyDefaultHierarchyTemplate()

sourceSets { commonMain { dependencies { api(libs.compose.runtime) } } }
sourceSets {
commonMain { dependencies { api(libs.compose.runtime) } }
// We use a common folder instead of a common source set because there is no commonizer
// which exposes the browser APIs across these two targets.
jsMain { kotlin.srcDir("src/browserMain/kotlin") }
val wasmJsMain by getting { kotlin.srcDir("src/browserMain/kotlin") }
}

targets.configureEach {
compilations.configureEach {
Expand Down
10 changes: 4 additions & 6 deletions circuit-runtime-ui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@ kotlin {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
if (hasProperty("enableWasm")) {
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
// endregion

Expand Down
10 changes: 4 additions & 6 deletions circuit-runtime/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@ kotlin {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
if (hasProperty("enableWasm")) {
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
// endregion

Expand Down
14 changes: 8 additions & 6 deletions circuit-test/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,10 @@ kotlin {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
if (hasProperty("enableWasm")) {
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
// endregion

Expand Down Expand Up @@ -63,6 +61,10 @@ kotlin {
}
val jvmTest by getting { dependsOn(commonJvmTest) }
val androidUnitTest by getting { dependsOn(commonJvmTest) }
// We use a common folder instead of a common source set because there is no commonizer
// which exposes the browser APIs across these two targets.
jsMain { kotlin.srcDir("src/browserMain/kotlin") }
val wasmJsMain by getting { kotlin.srcDir("src/browserMain/kotlin") }
}
}

Expand Down
31 changes: 25 additions & 6 deletions circuitx/effects/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,23 @@ kotlin {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
}
if (hasProperty("enableWasm")) {
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser()
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
moduleName = property("POM_ARTIFACT_ID").toString()
browser {
testTask {
useKarma {
useChromeHeadless()
useConfigDirectory(
rootProject.projectDir
.resolve("internal-test-utils")
.resolve("karma.config.d")
.resolve("wasm")
)
}
}
}
binaries.executable()
}
// endregion

Expand All @@ -54,7 +65,7 @@ kotlin {
}
}
val iosTest by getting { dependencies { dependsOn(commonTest) } }
val jsTest by getting { dependencies { dependsOn(commonTest) } }
val browserTest by creating { dependencies { dependsOn(commonTest) } }
val jvmTest by getting { dependencies { dependsOn(commonTest) } }
val androidUnitTest by getting {
dependsOn(commonTest)
Expand All @@ -65,6 +76,10 @@ kotlin {
implementation(libs.androidx.compose.ui.testing.manifest)
}
}
// We use a common folder instead of a common source set because there is no commonizer
// which exposes the browser APIs across these two targets.
jsTest { kotlin.srcDir("src/browserTest/kotlin") }
val wasmJsTest by getting { kotlin.srcDir("src/browserTest/kotlin") }
}
targets.configureEach {
compilations.configureEach {
Expand All @@ -73,6 +88,10 @@ kotlin {
}
}

// adding it here to make sure skiko is unpacked and available in web tests
// https://github.com/JetBrains/compose-multiplatform/issues/4133
compose.experimental { web.application {} }

android {
namespace = "com.slack.circuitx.sideeffects"

Expand Down
Loading
Loading