Home > Software design >  How do I include cross compiled client JS Code in the Fat Jar or the Docker Container on a Kotlin Mu
How do I include cross compiled client JS Code in the Fat Jar or the Docker Container on a Kotlin Mu

Time:11-08

I inherited a small Kotlin Multiplatform/Ktor application a little while ago and and moving on to deployment. I'm extremely new to this framework, and gradle as a whole and I'm having issues including cross compiled client side JS inside of the Fat Jar or the Docker Container. I'm not entirely sure if this is supposed to be inside of the Fat Jar, or if I include the JS into the Docker Container, I just know that it doesn't work like it is supposed to and I can't find any good documentation or answers to these questions.

The error I get specifically is this: The HTML Page errors here:

<script>
    Application.main();
</script>

Uncaught Reference Error: Application is not defined

The page works normally and the function is called when running the project in Intellij IDEA, but when I create a Docker Image out of the Fat Jar, or run it outside of IDEA, it no longer works. Here is the Kotlin source file just for posterity:

(In Application.kt)

fun main() {

Here's my Dockerfile:

FROM gradle:7-jdk11 AS build
COPY --chown=gradle:gradle . /home/gradle/src
WORKDIR /home/gradle/src
RUN gradle buildFatJar --no-daemon

FROM openjdk:11
EXPOSE 8080:8080
RUN mkdir /app
COPY --from=build /home/gradle/src/build/libs/MyApp.jar /app/MyApp.jar
ENTRYPOINT ["java","-jar","/app/MyApp.jar"]

Here's my build.gradle.kts:

import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpack

val kotlinVersion = "1.7.20-Beta"
val ktorVersion = "2.0.3"
val kotlinxHtmlVersion = "0.8.0"
val kotlinxCoroutinesVersion = "1.3.8" //curr: 1.6.4
val kotlinWrappersVersion = "1.0.0-pre.354"
val logbackVersion = "1.2.3" //11

plugins {
    id("io.ktor.plugin") version "2.1.3"
    kotlin("multiplatform") version "1.7.20-Beta"
    application
}

application {
    mainClass.set("com.example.application.ServerKt")
}

ktor {
    fatJar {
        archiveFileName.set("MyApp.jar")
    }
}

group = "com.example"
version = "1.0-SNAPSHOT"

repositories {
    jcenter()
    mavenCentral()
}

dependencies {
    implementation("org.apache.commons:commons-email:1.5")
}

kotlin {
    jvm {
        compilations.all {
            kotlinOptions.jvmTarget = "1.8"
        }
        withJava()
        testRuns["test"].executionTask.configure {
            useJUnitPlatform()
        }
    }
    js {
        browser {
            binaries.executable()
            dceTask {
                keep("Application.columnIndex", "Application.prepareTable")
            }
        }
    }
    sourceSets {...} // excluded for brevity
}

// include JS artifacts in any JAR we generate
tasks.getByName<Jar>("jvmJar") {
    val taskName = if (project.hasProperty("isProduction") || project.gradle.startParameter.taskNames.contains("installDist")) {
        "jsBrowserProductionWebpack"
    } else {
        "jsBrowserDevelopmentWebpack"
    }
    val webpackTask = tasks.getByName<KotlinWebpack>(taskName)
    dependsOn(webpackTask) // make sure JS gets compiled first
    from(File(webpackTask.destinationDirectory, webpackTask.outputFileName)) // bring output file along into the JAR
}

tasks {
    withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
        kotlinOptions {
            jvmTarget = "1.8"
        }
    }
}

distributions {
    main {
        contents {
            from("$buildDir/libs") {
                rename("${rootProject.name}-jvm", rootProject.name)
                into("lib")
            }
        }
    }
}

// Alias "installDist" as "stage" (for cloud providers)
tasks.create("stage") {
    dependsOn(tasks.getByName("installDist"))
}

tasks.getByName<JavaExec>("run") {
// so that the JS artifacts generated by `jvmJar` can be found and served
    classpath(tasks.getByName<Jar>("jvmJar")) 
}

After running the docker file, which runs buildFatJar, it copies the created Jar to the Docker Image, then I manually add in static files & some .json files and all of that works fine; however, I cannot for the life of me get the cross compiled JS to work or be referenced in the application.

TLDR: How can I include the cross compiled JS either within the Fat Jar itself or in the Docker container?

I've already tried this: https://stackoverflow.com/questions/61245847/create-fat-jar-from-ktor-kotlin-multiplatform-project-with-kotlin-gradle-dsl and many variations of this plus several other stack overflow answers and nothing seems to work for me.

I have tried several different methods to get the Cross compiled JS into the fat jar, and when that failed, I tried taking the contents of the build/js/ directory and copying them into the docker container instead to see if that would allow it to run and neither method worked for me.

I've been looking for answers on this all day and have found nothing, so any suggestions would be helpful, thanks!

CodePudding user response:

Turns out the answer was actually pretty simple. The file from /build/distributions called Application.js needed to be moved into the project and in a place where the server could see & serve it, i.e. /static/script.

Leaving this up here because this isn't documented anywhere and I spent like 4 hours looking for a solution, so I hope this helps people in the future.

  • Related