https://kotlinlang.org logo
#webassembly
Title
# webassembly
d

Darryl Miles

03/06/2024, 4:50 PM
With kotlin-wasm-example the browser-example, the default output artifacts appear to have some kind of metadata "colouring", such as "kotlin-metadata" and "kotlin-runtime" and "kotlin-api". This colouring appear to prevent the output artifact from being consumed in another project. How do you export a regular standard JAR, with standard colouring, such that it can be consumed by a sibling Gradle project ? I have a task that generates the necessary format, the task is also attached to a configuration "webjar", so I am expecting to be able to consume it with:
implementation(project(":web-wasm"), configuration = "webjar")
But a bunch of errors concerning compatibilities are emitted. I guess the colouring is to record wasm target platform info and ensure compatibility, but I don't think I need this and want to press the feature kill switch on it, and have it just export the JAR. The only alternative solution I have is to copy the output JAR to somewhere outside of the project tree, and manually use a file JAR reference in dependencies. `implementation(files(layout.projectDirectory.file("../web-wasm-0.0.1-SNAPSHOT-webjar.jar")))`` Alternatively how do I describe the correct and compatible artifact colouring metadata that will allow it to match and be consumed, like a normal sibling dependency.
Have not managed to resolve getting it to consume output JARs as-is, I am using a "tmp" dir to generate into. So the WAR project can consume with
implementation(file("path/here.jar"))
The only other drawback is I need to build the gradle project twice, as gradle detects if the JAR file timestamp/contents changed under the build, when not expected. The use of
onlyIf
directive makes it skip the copy the 2nd/Nth build around. But it works and I can include WASM with HEAD section
<script src="/mount/webjars/my-app-thingy/1/wasmJs/productionExecutable/web-asm-alongside.js">
and it load the WASM from the URL alongside the bootstrap JS (also from the webjar) Here is the WIP version so far in WASM builder project, that work well enough to consume it like any other webjar.
Copy code
// We setup an additional configuration just containing the webjar
//  in an ideal world you should be able to just consume this with:
//  implementation(project(":this-wasm-project"), configuration = "webjar")
// But some kind of output artifact capability colouring prevents this
//  so we have to copy the webjar an intermediate location outside of
//  the project buildDirectory and have the consuming use the raw path:
//  implementation(file("/location/to/raw/path/tmp/my-project.jar"))
val webjar by configuration.creating

// This is only needed due to not being able to consume artifacts directly by sibling
val fileCopyWebjarTask = tasks.register<Copy>("fileCopyWebjar") {
    group = "group"
    doNotTrackState("Outside projectDirectory")
    // finalizeBy is always run, attempt to skip this task if
    onlyIf { tasks.findByName("webjar")?.didWork == true }
    from(layout.buildDirectory.dir("libs"))
    into(layout.projectDirectory.dir("../tmp"))
    include("*-webjar.jar")
}

val WEBJAR_version = "1"
val WEBJAR_path = "my-app-thingy/$WEBJAR_version"
val WEBJAR_subpath = "wasmJs/productionExecutable"
val webjarTask = tasks.register<Jar>("webjar") {
    // Maybe this is the wrong task to depend on (maybe a better task to depend on exists upstream)
    dependsOn(tasks.findByName("wasmJsBrowserDistribution"))
    group = "build"
    // Have to use classifier, because some other JAR artifact is the default
    archiveClassifier = "webjar"
    // This doesn't conform to any standard, some metadata to help allow identification and bootstrap at runtime
    manifest {
        attributes["X-WebJar-Name"] = "my-app-thingy"
        attributes["X-WebJar-Path"] = WEBJAR_path
        attributes["X-WebJar-Subpath"] = WEBJAR_subpath
        attributes["X-WebJar-Version"] = WEBJAR_version
        attributes["X-WebJar-WASM-Loader"] = "$WEBJAR_path/$WEBJAR_subpath/web-wasm.js"
        attributes["X-WebJar-WASM-Binary"] = "$WEBJAR_path/$WEBJAR_subpath/my-app-thingy-web-wasm-wasm-js.wasm"
    }
    from(layout.buildDirectory.dir("dist")) {
        into("META-INF/resources/webjars/${WEBJAR_path}")
    }
    from(layout.buildDirectory.file("dist/wasmJs/productionExecutable/web-wasm.js")) {
        // By default, even if we load the JS from a webjar path, it will attempt to load the WASM binary
        //  from the consuming URL location.href/base and not from along-side the JS loader.
        // So this provides an additional copy of the bootstrap that is edited
        into("META-INF/resources/webjars/${WEBJAR_path}/${WEBJAR_subpath}")
        rename("web-wasm.js", "web-wasm-alongside.js")
        // TODO, investigate use of a script snippet like this below
        // if(document.currentScript) document.currentScript.getAttribute('src')
        // edited(document.currentScript) + "./my-app-thingy-web-wasm-wasm-js.wasm"
        // this might allow standard bootstrap code to exist which doesn't hardwire into the webjar the "/static/" part, instead it would resolve the alongside fully qualified URL based on the locate of the JS bootstrap
        //
        // TODO fixup/create web-wasm-alongside.js.map
        // const d = "./my-app-thingy-web-wasm-wasm-js.wasm", _ = {js_code: r};
        filter(Transformer { line -> line.replace(Regex("\"\\./(\\S+)\\.wasm\""), "\"/static/webjars/$WEBJAR_path/$WEBJAR_subpath/\$1.wasm\"") })
    }
    finalizedBy(fileCopyWebjarTask)
}

artifacts.add(webjar.name, layout.buildDirectory.dir("libs/")) {
    builtBy(webjarTask)
}
a

Artem Kobzar

03/07/2024, 9:38 AM
@Ilya Goncharov [JB] ^^
d

Darryl Miles

03/07/2024, 11:47 AM
To also add, if you inspect the JS loader (web-wasm.js) there appears to be additional code inserted for NodeJS and development environments. I would like to disable this section of JS bootstrap from making it into the published webjar. As it can only present an unexpected risk/concern at the point of consumption. One section exposes an absolute path and persons identity (which is part of path) inside the JS that you might not want to be published by default. So it would be good to make each section of the WASM location resolver in the loader configurable, as to whether it is emitted in the output JS. This might also allow the current basic loader of fetch WASM from current location.href/base to be enabled/disabled. Then separately as an option build in an alternative loader (which my Regex is a crude implementation of) that resolve an alongside URL. But then you might allow extension interface (in gradle config/project file contribution) to contribute a custom loader snippet, for example secure loader, that validates WASM blob SHA1/digest matches that expected by JS. I am currently researching/understanding how debug/diagnostic works and can still be provided, even when the WASM webjar is in production. It is not clear at this time how the stacktrace info can be interpreted into various levels of diagnostic. Ideally this would also be a configurable thing too, to allow zero diagnostic high security mode, a basic, a standard and a detailed (file/lineno/methodname/argument info/more?)
19 Views