From a0dce39925dfeed86df5d233aa3c7b67001b27cb Mon Sep 17 00:00:00 2001 From: Terry Yiu Date: Tue, 15 Sep 2026 15:43:18 +0300 Subject: [PATCH] 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 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 --- webApp/build.gradle.kts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/webApp/build.gradle.kts b/webApp/build.gradle.kts index e668023..1852c0e 100644 --- a/webApp/build.gradle.kts +++ b/webApp/build.gradle.kts @@ -53,6 +53,27 @@ mapOf( 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 -> + """ """ + } + val html = indexHtml.readText() + if ("rel=\"preload\"" !in html) { + indexHtml.writeText(html.replaceFirst("", "$preloadLinks\n")) + } + } + } } } } \ No newline at end of file