This is mostly notes to myself in case I ever do this again.
So, I've been doing a lot of frontend development in Typescript/React, but I'm also interested in Kotlin. I'm a full-stack engineer, so the idea of making a whole application in the same language appeals to me. (I'm aware that backend development in NodeJS exists, but I'm a fan of strict typing.)
So I've been exploring Kotlin's JS backend support.
So anyways, here's how my project is set up right now:
From IntelliJ Ultimate, I created a Gradle Kotlin Multiplatform project, and I immediately commented-out the JVM and "common" bits of the Gradle file. I figured if I end up wanting to re-add backend stuff later, it's easier to start with Multiplatform rather than retrofit it back in.
I also changed the names of all the base project folders so they wouldn't accidentally be picked up. I didn't need the Ktor sample code because I'm not using a framework for this particular thing, but I wanted to keep it around for a little while in case I decide to pick it up later.
Since I didn't need to run the full stack, I created an index.html
file inside of jsMain/resources/
and use this command to just run the dev server:
./gradlew jsBrowserRun --stacktrace --info --continuous
Soon I realized that my development flow doens't quite fit the norm, and I found myself wanting to change the default dev server behavior. It was hard.
(Specifically, I wanted to disable the devServer.open
flag so it would stop opening a new browser tab
every time I ran a build. But if you want to, say, change the dev server port from the default value of
8080, you'd have to follow similar steps.)
So you see, for better and for worse the default Kotlin/JS project uses Webpack to bundle your compiled
Javascript code with the Kotlin/JS runtime into the final bundle you can load into a browser.
The Gradle build literally creates a NodeJS project inside the build/
directory and runs npm install
to download the 340 dependencies required by Webpack
(on top of the stuff Gradle pulls in in order to build Kotlin). So even though you're avoiding NPM bloat
in your runtime code, you can't escape it in your build.
Since this Webpack config is generated by Gradle on every build, you can't just go in and change it
(it lives inside /build/js/packages/${project name}/webpack.config.js
) because it'll just be
overwritten. Also, you should never hand-modify a build asset.
Instead, you should modify the Gradle task. This is the magic code to do it. Add this to your build.gradle if you chose to use the Groovy DSL like I did. (Kotlin would have it SO much easier.)
// The Kotlin Gradle Plugin generates a KotlinWebpack task (suffixed "*Run") for every KotlinBrowserJs DSL
// block (kotlin > js > browser) that exists (hence jsBrowserRun).
jsBrowserRun {
// Disable Webpack's DevServer.open property, which drives me insane.
boolean open = false;
// This is ridiculous, but it's the only way I've figured out how to do it.
//
// A KotlinWebpack task builds writes-out a webpack configuration file when it runs.
// The "devServer" block is represented by a data class (KotlinWebpackConfig.DevServer)
// with immutable properties. The only way to change it is to instantiate a new one or
// to copy() it.
//
// Since we're running this Groovy and not Kotlin though, we don't get Kotlin's default
// variables (i.e. .copy(open = false)). We have to pass *every value* of the devServer
// block back into it.
//
// The definition of the DevServer object is here:
// https://github.com/JetBrains/kotlin/blob/3e251668324f3ac2f0819ca6bef0ce97ca26f39b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/webpack/KotlinWebpackConfig.kt
devServer = devServer.copy(
devServer.inline,
devServer.lazy,
devServer.noInfo,
open,
devServer.overlay,
devServer.port,
devServer.proxy,
devServer.contentBase,
)
}
Have you been able to run React app with Kotlin Multiplatform project? I'm trying for a few days without success