Files
sats-price/webApp/build.gradle.kts
T
tyiuandClaude Sonnet 5 a0dce39925
Deploy website to GitHub Pages / deploy (push) Canceled after 0s
Preload Compose Skia WASM chunks to fix serial load waterfall
Skiko's Skia-via-WebAssembly runtime is loaded through a dynamic
import() inside webApp.js, so the browser previously only discovered
those multi-MB .wasm files after fully downloading and executing the
JS bundle. Inject <link rel="preload"> hints for the content-hashed
.wasm output into index.html after each browser distribution build so
the WASM fetch starts in parallel with the JS instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 15:43:18 +03:00

79 lines
3.1 KiB
Kotlin

import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.mokoResources)
}
kotlin {
js {
browser()
binaries.executable()
}
@OptIn(ExperimentalWasmDsl::class)
wasmJs {
browser()
binaries.executable()
}
sourceSets {
commonMain.dependencies {
implementation(project(":shared"))
implementation(libs.compose.ui)
implementation(npm("@js-joda/timezone", "2.25.1"))
}
}
}
multiplatformResources {
resourcesPackage.set("xyz.tyiu.satsprice.webApp")
}
// moko-resources loads string/plural/image/file resources at runtime via fetch('./localization/...'),
// not via a webpack import, so webpack's production bundler has no reason to know that directory
// exists and never copies it into the distribution. (The dev server happens to serve it anyway, since
// it serves the whole compiled package directory as static files — which is why this only breaks in
// production.) Copy it into the actual distribution output ourselves so a static production build is
// self-contained.
val npmPackageName = "${rootProject.name}-${project.name}"
mapOf(
"wasmJsBrowserDistribution" to "wasm",
"jsBrowserDistribution" to "js",
).forEach { (taskName, targetDir) ->
tasks.named<Sync>(taskName) {
val localizationSrc = rootProject.layout.buildDirectory
.dir("$targetDir/packages/$npmPackageName/kotlin/localization")
doLast {
val src = localizationSrc.get().asFile
if (src.exists()) {
src.copyRecursively(outputs.files.singleFile.resolve("localization"), overwrite = true)
}
// Compose Multiplatform renders via Skia compiled to WebAssembly (several MB), which is
// loaded through a dynamic import() from inside webApp.js. Without a hint, the browser only
// discovers those .wasm files after it has downloaded, parsed, and started executing the JS
// bundle — a fully serial waterfall. Preloading lets the browser fetch the .wasm in parallel
// with the JS instead. The filenames are content-hashed by webpack, so they're discovered
// here rather than hardcoded in the static index.html source.
val distDir = outputs.files.singleFile
val indexHtml = distDir.resolve("index.html")
if (indexHtml.exists()) {
val wasmFiles = distDir.listFiles { f -> f.extension == "wasm" }.orEmpty().sortedBy { it.name }
if (wasmFiles.isNotEmpty()) {
val preloadLinks = wasmFiles.joinToString("\n") { f ->
""" <link rel="preload" href="${f.name}" as="fetch" type="application/wasm" crossorigin>"""
}
val html = indexHtml.readText()
if ("rel=\"preload\"" !in html) {
indexHtml.writeText(html.replaceFirst("</head>", "$preloadLinks\n</head>"))
}
}
}
}
}
}