varabyte / kobweb

A modern framework for full stack web apps in Kotlin, built upon Compose HTML
https://kobweb.varabyte.com
Apache License 2.0
1.4k stars 63 forks source link

Investigate different bundlers / remove Webpack integration #330

Open bitspittle opened 9 months ago

bitspittle commented 9 months ago

Right now, Kobweb uses Webpack to bundle JS code because, well, this is the automatic solution provided by the JB Compose Plugin.

Bun recently took the JS world by storm, and they are promising an insanely fast bundler: https://bun.sh/blog/bun-bundler

There's a chance with Bun we could simultaneously drop export compile times down significantly while also, maaaaybe, ending up with smaller JS files? Worth investigating!

esbuild might also be a solution here.

As far as I understand it, we don't actually need most of what Webpack offers since Kobweb runs its own web server directly.

To get this to work, you'd need to look into the Kobweb Gradle Application Plugin, see how it's currently triggering webpack tasks, disable that path, and slip in a different bundler instead.

RyuuyaS commented 9 months ago

Saw this in Youtrack but seems to be that Kotlin/JS team would not invest to this shortly. Vite seems to be good too.

bitspittle commented 9 months ago

Yeah! I'm aware of that issue and Kotlin/JS's lack of movement on it.

This is definitely something Kobweb can work around, and if we get it working, it would definitely be an additional perk to using Kobweb over vanilla Compose HTML.

There's already a case where the Kobweb Gradle plugin stomps over a mistake made by the Kotlin/JS team (around webpack config and live reloading, see here).

I experimented before and got pretty close to ripping out webpack and only using rollup, but I ran into issues around JS modules that I could never quite figure out, and since this wasn't critical path stuff, I had to cut my experiment short and move on.

I looked into Vite actually. Kobweb runs its own server so it doesn't need the webserver part of Vite, which just leaves the bundler, which is esbuild.

bitspittle commented 9 months ago

I spent today exploring esbuild (and also esbuild + terser), comparing the speed and size of the final JS file it creates compared to what I'm currently getting with webpack (the default system provided by the Kotlin/JS folks)

I worked with a test project that, with webpack, was generating an output file at 937K, and esbuild was 105K. It was a 12% increase in size. Applying terser after the face resulted in a larger size??

esbuild was FAST, but this initial exploration makes me think I'll be leaving things with webpack for now. I don't think speed matters too much with the export process, because it happens so rarely, and bundling is probably not the most significant, expensive step in an export anyway.

Full disclosure, I'm far from an expert, so maybe I configured something wrong. I'll include the code below I used as the way I explored this issue.


import com.varabyte.kobweb.gradle.application.util.configAsKobwebApplication
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsExec

plugins {
    alias(libs.plugins.kotlin.multiplatform)
    alias(libs.plugins.jetbrains.compose)
    id("com.varabyte.kobweb.application")
    id("com.varabyte.kobwebx.markdown")
}

group = "playground"
version = "1.0-SNAPSHOT"

kobweb {
    markdown {
        imports.add(".components.widgets.*")
    }
    kspProcessorDependency.set("com.varabyte.kobweb:project-processors")
}

kotlin {
    configAsKobwebApplication(includeServer = true)

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation(compose.runtime)
            }
        }
        val jsMain by getting {
            dependencies {
                implementation(compose.html.core)
                implementation("com.varabyte.kobweb:kobweb-core")
                implementation("com.varabyte.kobweb:kobweb-silk")
                implementation("com.varabyte.kobwebx:silk-icons-fa")
                implementation("com.varabyte.kobwebx:kobwebx-markdown")
                implementation(project(":sitelib"))
                implementation(devNpm("esbuild", "0.19.4"))
                implementation(devNpm("terser", "5.20.0"))
            }
        }
        val jvmMain by getting {
            dependencies {
                implementation("com.varabyte.kobweb:kobweb-api")
                implementation(project(":sitelib"))
            }
        }
    }
}

val mainCompilation = kotlin.js().compilations.findByName("main")
    .let {
        checkNotNull(it) { "Could not find main compilation" }
    }

val esbuildBundleTask = NodeJsExec.create(mainCompilation, "esbuildBundle") {
    group = "build"
    description = "???"

    val esbuildPath = project.rootProject.layout.buildDirectory
        .file("js/node_modules/.bin/esbuild")
        .get().asFile.absolutePath

    val kotlinJsOutputDir = project.layout.buildDirectory.dir("compileSync/js/main/productionExecutable/kotlin").get()
    val esbuildOutputFile = project.layout.buildDirectory.file("esbuild/output.js").get()

    inputs.dir(kotlinJsOutputDir)
    outputs.file(esbuildOutputFile)

    this.nodeArgs = mutableListOf(
        esbuildPath,
        "--bundle",
        "--minify",
        kotlinJsOutputDir.file("playground.js").asFile.absolutePath,
        "--outfile=${esbuildOutputFile.asFile.absolutePath}")

    println("Generating ${esbuildOutputFile.asFile.absolutePath}.")
}

NodeJsExec.create(mainCompilation, "terserDce") {
    group = "build"
    description = "???"

    val terserPath = project.rootProject.layout.buildDirectory
        .file("js/node_modules/.bin/terser")
        .get().asFile.absolutePath

    dependsOn(esbuildBundleTask)

    val terserOutputFile = project.layout.buildDirectory.file("terser/output.js").get()

    val esbuildBundleOutput = esbuildBundleTask.get().outputs.files.singleFile
    inputs.file(esbuildBundleOutput)
    outputs.file(terserOutputFile)

    this.nodeArgs = mutableListOf(
        terserPath,
        esbuildBundleOutput.absolutePath,
        "--output",
        terserOutputFile.asFile.absolutePath,
    )

    println("Generating ${terserOutputFile.asFile.absolutePath}.")
}

Used here.

apatrida commented 1 month ago

see kotlin-vite new plugin: https://gitlab.com/opensavvy/automation/kotlin-vite