Kotlin-js, dce & closure compiler

I’ve been playing with kotlin-js (see the ui directory in this project). In the documentation it is suggested that using the google closure compiler & dce plugins might be a good thing for production code. I’m struggling with both and the documentation is less than helpful on this front currently and there seems to be a general lack of examples using both. Google is of course less than helpful when most documentation for this is years old and not relevant for current versions of kotlin-js.

Basically the only thing that actually works in a browser is the enormous js file produced by the kotlin-js file. I’d like a properly minified version of that with source maps please.

Here’s my gradle file. I’m attempting to import left-pad as a proof of concept. I’ve enabled dukat via kotlin.js.experimental.generateKotlinExternals=true in gradle.properties.

plugins {
    id("org.jetbrains.kotlin.js") version "1.3.61"
}

apply {
    // this plugin minifies the kotlin js output. Several bugs currently with this (e.g. multiple npms having an index.js)
    plugin("kotlin-dce-js")
}

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

repositories {
    jcenter()
}

val closureCompiler by configurations.creating

dependencies {
    implementation(kotlin("stdlib-js"))
    testImplementation(kotlin("test-js"))
    // needed for the google closure compiler, which is recommended to minify the output of dce
    closureCompiler("com.google.javascript:closure-compiler:v20200112")
}

kotlin {
    sourceSets["main"].dependencies {
        // set kotlin.js.experimental.generateKotlinExternals=true in gradle.properties for dukat to do its thing

        implementation(npm("left-pad", "1.3.0"))
        implementation("org.jetbrains.kotlinx:kotlinx-html-js:0.6.12")
        implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.3.3")
    }
    target {
        useCommonJs()

        browser {
            webpackTask {

            }
        }
    }
}

tasks {
    register<Copy>("copy-index-html") {
        from("src/main/resources/index.html")
        into("build/packaged")
    }

    register<Copy>("copy-dce-html") {
        from("src/main/resources/dce.html")
        into("build//kotlin-js-min/main")
    }
    // does not work because of module errors
    register<JavaExec>("googleClosureCompileDceJs") {
        dependsOn("copy-index-html","copy-dce-html","assemble","runDceKotlin")
        classpath = closureCompiler
        main = "com.google.javascript.jscomp.CommandLineRunner"
        args = listOf(
            "--compilation_level=SIMPLE_OPTIMIZATIONS",
//            "--process_common_js_modules", // errors with a  ERROR - [JSC_INVALID_MODULE_PATH] Invalid module path "kotlin" for resolution mode "BROWSER"
            // nope, this doesn't work either
            "--js_output_file=build/packaged/${rootProject.name}.js",
            "build/kotlin-js-min/main/kotlin.js",
            "build/kotlin-js-min/main/kotlinx-coroutines-core.js",
            "build/kotlin-js-min/main/kotlinx-html-js.js",
            // this is left-pad, which the dce plugin fails to name properly. Also, you can only have 1 file called index.js
            "build/kotlin-js-min/main/index.js",
            "build/kotlin-js-min/main/${rootProject.name}.js"
        )
    }

    // processes the output in distributions (12M) and produces a similarly sized file that does not work (unlike the original)
    register<JavaExec>("googleClosureCompileDistributionJs") {
        dependsOn("copy-index-html","assemble")
        classpath = closureCompiler
        main = "com.google.javascript.jscomp.CommandLineRunner"
        args = listOf(
            "--compilation_level=SIMPLE_OPTIMIZATIONS",
            "--js_output_file=build/packaged/${rootProject.name}.js",
            "build/distributions/${rootProject.name}.js"
        )
    }
}

There are multiple problems that I’m chasing with this.

  • gradle run --continuous works fine but the output in distributions is huge.
  • the dce output is a lot smaller but I’ve yet to find a way to make it work. I attempted to load this via the dce.html that explicitly names each module in the (supposed) order they need to be loaded.
  • I’ve defined two variants of using the closure compiler.
    • The first one works on the dce output and this doesn’t load (some error about modules when you load the html).
    • The second one works on the output in distributions (non dce) this produces a similarly huge file that unlike the original does not load

Both of the tasks copy files to a packaged directory.

I’d appreciate feedback on how to improve this and get it in a working state.

I’d also humbly suggest updating the documentation with working configuration for dce and the closure compiler. Even what I did seems useful (despite not producing useful output) since it at least has a working config for pulling in the closure compiler, which is something that required a bit of research on my side as absolutely nothing with the kotlin dsl is straightforward.

Currently the tests don’t work. I suspect the leftpad dependency is guilty.

2 Likes

I’ve been struggling with getting this set up too. The documentation is quite lacking and there are bugs everywhere. In this particular case I was able to solve it by following these two tutorials:

  1. https://kotlinlang.org/docs/tutorials/javascript/setting-up.html (plus next steps in the tree)
  2. Kotlin Playground: Edit, Run, Share Kotlin Code Online

Specifically, looking over your example, I think you should:

  • remove the kotlin-dce-js plugin. DCE and minify is handled automatically by the webpack task.
  • remove the closurecompiler dep and configuration; handled automatically by the webpack task.
  • remove the extra tasks you’ve added to copy stuff into build/repackaged; the webpack task will take care of copying everything into build/distributions which is similar
  • for development, use ./gradlew run (for multiplatform: ./gradlew jsBrowserRun) which will start the webpack devserver. Webpack will bundle all your js code (including kotlin.js) into a single file which is named according to the gradle name of your module (no package or project is in the filename). So make sure src/main/resources/index.html contains a script tag that imports “foo.js” where foo is the raw name of your module with no project.
  • for production, use ./gradlew build (or ./gradlew assemble to skip running tests) which will populate build/distributions with the dce’d, minified source code and resources.

Hope this helps!

@adorokhine Hey, where do you see evidence of closurecompiler being handled by the webpack task? Grepping the source of KotlinWebpack and KotlinWebpackConfig, I’m not seeing the word “closure” anywhere. I’ve been enjoying building with kotlinjs, but I really need to get the size of the frontend down a chunk from where it is, which is what led me to this thread.