https://kotlinlang.org logo
#ktor
Title
# ktor
g

Gunslingor

08/10/2020, 2:00 PM
browserdevelopmentrun is a task that I think comes from KotlinJS plugin. I think it runs the front end only, but then how does this really provide any usefulness when routing is in the backend Ktor? ie. I have the task config, what url do I go to to see the page?
Copy code
target {
    browser {
        webpackTask {
            outputFileName = "MyProjectName.js"
        }
        runTask {
            dependsOn("build")
            devServer = devServer?.copy(
                port = 8088,
                proxy = mapOf("/api" to "<http://localhost:8080>")
            )
        }
    }
}
c

Casey Brooks

08/10/2020, 2:41 PM
You’d probably want to use the
browserProductionWebpack
task to generate a static
.js
file, bundle that with your server’s resources, and serve it as a static file from Ktor itself. There’s probably a better way to do it, but here’s how I have this set up for my site: https://github.com/cjbrooks12/caseyjbrooks/blob/master/lib/build.gradle.kts#L79-L81 adds
/browser.js
to the jar resources, so I can serve the following in my ktor router
Copy code
routing {
    static {
        resource("/favicon.ico")
        resource("/browser.js")
        resource("/browser.js.map")
    }
}
g

Gunslingor

08/10/2020, 2:49 PM
hmmmmm
I have a fatJar maker working, I'm using subprojects instead of multiplatform plugin. Sub wondering if there is a faster was to spin things up while testing. That runTask above did the trick until my routes where in ktor. stuff multiplatform maybe took care of, lol
c

Casey Brooks

08/10/2020, 3:02 PM
I havent played around with this setup too much, and I’m wondering myself about how to easily set it up to use the dev servers for development and the static bundle for deploying in prod. But it seemed like even using
browserProductionRun
, the page load time was very long compared to serving the static bundle directly, and I prefer to just recompile everything on changes. But yeah, i don’t think there’s any automatic integration of Ktor with MPP right now to serve the JS bundles through/with Ktor; it’s all manual
r

Robert Jaros

08/10/2020, 3:04 PM
@Gunslingor You should run two gradle processes (in two console/terminal windows). One with
browserDevelopmentRun
task and the other with
run
or
jvmRun
(not sure what the name is) to run Ktor backend.
You will be able to run webpack dev server on port 8088, and webpack will proxy api calls to ktor on port 8080.
g

Gunslingor

08/10/2020, 3:06 PM
Ah! Kotlin Jedi! That helps massively... I can run browserDevRun and then jvmRun separately... cool, makes sense, no idea how that actually ends up working... and you just explained how that works, lol. THANK YOU!
r

Robert Jaros

08/10/2020, 3:07 PM
And you will probably want third gradle process as well, just to compile backend classes and to have hot reload on the backend.
g

Gunslingor

08/10/2020, 3:08 PM
Copy code
tasks {
    register<Copy>("prepCopyJsBundleToKtor") {
        group = "app"
        dependsOn("srcFrontend:build", "srcBackend:build")
        mustRunAfter("srcFrontend:build", "srcBackend:build")
        from("$buildDir/frontend/distributions/")
        include("**/*.*")
        into("$buildDir/backend/resources/main/web")
    }
    register("pipelineFatJarBuild"){
        group = "app"
        dependsOn("prepCopyJsBundleToKtor", "srcBackend:fatJarBuild")
        mustRunAfter("prepCopyJsBundleToKtor", "srcBackend:fatJarBuild")
    }
    register<JavaExec>("pipelineFatJarRunLocal") {
        group = "app"
        dependsOn("pipelineFatJarBuild")
        mustRunAfter("pipelineFatJarBuild")
        classpath = getByPath("srcBackend:fatJarBuild").outputs.files
    }
}
Current custom tasks for all (sub)projects
pipelineFatJarRun is my prod, I should be able to create a dev one now
r

Robert Jaros

08/10/2020, 3:11 PM
and you don't need to serve any js/css/other resources when running Ktor for development, because it won't be used anyway
frontend will be served by webpack
g

Gunslingor

08/10/2020, 3:12 PM
yeah, so no copy task needed
Lets see if this works:
Copy code
register<JavaExec>("pipelineDevRunLocal") {
    group = "app"
    dependsOn("jvmRun", "browserDevelopmentRun")
}
r

Robert Jaros

08/10/2020, 3:14 PM
such config works perfectly in kvision fullstack template - frontend hot reload with webpack and backend hot reload with ktor classpath watch.
in
application.conf
I have:
Copy code
ktor {
  deployment {
    port = 8080
    watch = [build/classes/kotlin/backend/main]
  }
}
g

Gunslingor

08/10/2020, 3:14 PM
I haven't touched kvision yet
yeah, it's a tough balance... using whats easy and doing whats hard so I understand a little more why it's so easy, lol
r

Robert Jaros

08/10/2020, 3:16 PM
You shouldn't run ktor development task and webpack development task with one command
g

Gunslingor

08/10/2020, 3:16 PM
two threads right, I shouldn't have to open two windows and press two buttons
r

Robert Jaros

08/10/2020, 3:18 PM
I didn't manage to make it work with only one window :-)
I use three separate windows for daily work
g

Gunslingor

08/10/2020, 3:19 PM
yeah, I have hundreds of windows... I try to minimize my run windows though, lol
r

Robert Jaros

08/10/2020, 3:20 PM
Let me know if you succeed with one window :-)
g

Gunslingor

08/10/2020, 3:20 PM
added to the TODOs, will let you know
actually I guess two makes sense, lol, can start/stop one only... run works but browserDevelopmentRun says the address is already in use, hmmm
listen EADDRINUSE: address already in use 127.0.0.1:8088
ah, run and browser are essentially the same... wrong subproject, dah
Getting close... looks like only the static routes are not working in this setup (as I suspected might happen)... hmmm.. might need to run my copy task
Sooo... if I change something like padding: 10px... that sure does take a while to compile deploy and test compared to good old css
Starting to wonder if my approach is wrong... is the intent of kotlin JS/CSS/HTML to completely program in kotlin and drop those, or is it to only program active content? I ask because the deeper I get the more it looks like the latter. I mean it isn't tough to define an object like document.create.div()... but it gets complicated when you start trying to combine them... so many appendChild, or nested document.create.something.... I think the nest is what ends up being required to combine elements. Or am I missing stuffthing?
I need to be able to define and combine div(){} type tagges and THEN document.create it, otherwise I have to nest document.creates when combining previsouly created elements in classes
r

Robert Jaros

08/10/2020, 5:07 PM
It's up to you in general. You can add some content to existing page layout, but you can also build the whole layout in Kotlin. I prefer to have whole app in Kotlin.
g

Gunslingor

08/10/2020, 5:08 PM
That's what I'm trying... I'm just wondering if its worth it. Longer compile time for something that didn't originally require compiling, and it seems a lot more complicated than combine HTML templates
r

Robert Jaros

08/10/2020, 5:09 PM
Of course you need nice DSL, but all kotlin/js frameworks have DSLs.
g

Gunslingor

08/10/2020, 5:09 PM
Does this look okay to you?
Copy code
class Table {
    val table = document.create.div("grid w-100 w-100 h-100") { }

    private fun sectionAdd(child: HTMLElement){
        val toolbarRow = document.create.div("row").apply {
            val toolbarRowCell = document.create.div("cell-12").apply {
                appendChild(child)
            }
            appendChild(toolbarRowCell)
        }
        table.appendChild(toolbarRow)
    }

    init {
        sectionAdd(TableToolbar().toolbar())
        sectionAdd(TableView().view)
        sectionAdd(TableToolbar().paginator())
    }
}
r

Robert Jaros

08/10/2020, 5:10 PM
no, it does not 🙂
c

Casey Brooks

08/10/2020, 5:10 PM
I’m generally working with the assumption that everything I do is within kotinx.html dsl. Nested “components” are just extensions on the TagConsumer or specific HTML tag class, and I basically rebuild and replace the entire screen with each update. Very coarse-grained HTML updates, and if I start to notice performance hits, then I’ll start looking into using a virtual DOM instead of the actual
document.appendChild { }
r

Robert Jaros

08/10/2020, 5:10 PM
you are not using DSL
g

Gunslingor

08/10/2020, 5:11 PM
Copy code
import kotlinx.html.dom.create
import kotlinx.html.js.div
import org.w3c.dom.HTMLElement
import kotlin.browser.document
import kotlin.dom.appendElement
isn't that DSL?
I mean hold on
Copy code
class TableToolbar {
    fun toolbar(): HTMLElement {
        return document.create.div("grid") {
            div("row") {
                div("cell-md-8 table-search-wrapper")
                div("cell-md-4 table-rows-wrapper")
            }
        }
    }

    fun paginator(): HTMLElement {
        return document.create.div("grid") {
            div("row") {
                p("cell-md-12 table-info-wrapper ")
            }
            div("row") {
                div("cell-md-12 table-pagination-wrapper")
            }
        }
    }
}
r

Robert Jaros

08/10/2020, 5:12 PM
it is, but you are not using it
g

Gunslingor

08/10/2020, 5:12 PM
That's DSL right?
r

Robert Jaros

08/10/2020, 5:12 PM
yes
g

Gunslingor

08/10/2020, 5:12 PM
So how does one now combine those two functiooons to produce one HTML ELEMENT
could be from different classes, in a higher level class
But if I can do it here in TableToolbar I should be able to do it anywhere
r

Robert Jaros

08/10/2020, 5:14 PM
I'm not using
kotlinx.html
so I wont tell you :-)
g

Gunslingor

08/10/2020, 5:14 PM
Not just combine, but potentially nest in bootstrap type rows
What you you using?
r

Robert Jaros

08/10/2020, 5:14 PM
KVision 🙂
g

Gunslingor

08/10/2020, 5:15 PM
ah... still haven't had a chance to dig into that. Is it better than what's above?
Everything is fine until I try to combine HTML, that sucks
r

Robert Jaros

08/10/2020, 5:18 PM
c

Casey Brooks

08/10/2020, 5:18 PM
Do you mean “combining HTML” as in using one custom function with kotlinx.html DSL inside another? You just need to have the function have a
TagConsumer<*>
as the receiver, and then you can call your custom functions inside each other
Copy code
fun render() {
    document.append {
        div("main") {
            parent {
                child()
                child()
                child()
                child()
            }
        }
    }
}

inline fun TagConsumer<*>.parent(crossinline block: ()->Unit) {
    div("parent") {
        block()
    }
}

fun TagConsumer<*>.child() {
    div("child"){}
}
g

Gunslingor

08/10/2020, 5:22 PM
I mean, I have classes, each class is a part of a page... so I can reuse the class on multiple pages, so I have an appbar, and a ribbon, and content and a foot... each a class, now I wanna combine them into a page and that page gets served up via ktor... I got it working above but dam its ugly
hmm... what is TagConsumer
hmm... I gotta run to lunch, but this sounds interesting. Are there docs on this to explain it so I don't have to bug u 2 much?
brb
c

Casey Brooks

08/10/2020, 5:55 PM
TagConsumer
is the base interface that kotlinx.html DSL functions are based on. When it is the receiver, it allows you to call any kotlinx.html functions you want, instead of having to dump everything into a single huge function or use selectors for each smaller chunk. Here’s some documentation on it (though it’s not great) https://github.com/Kotlin/kotlinx.html/wiki/Micro-templating-and-DSL-customizing
g

Gunslingor

08/10/2020, 6:11 PM
hmm... yeah, that looks like a gap in my education. I think when I was studying all this I was getting packages mixed up, ktor-html dsl, kotlin-html, kotlinx-html, kvision... feel like I'm missing some. Anyway, this looks great thank you, hope to put it into action.
So what happens if I want this function to apply to a DIV and a A, or say ALL HTMLElements (like most tags). Also, this notion always confuses me. What is really happening with the argument. I've read the kotlin docs like 8 times on this... the receiver is related to the argument somehow.
Copy code
fun DIV.paginator(block : DIV.() -> Unit)
hmmm.... and then I guess you can't use classes then... because I guess Kotlin would expect you to extend some existing class (DIV in this case) rather than use this.
I think I get it... I need combine raw components without a receiver in this case, so no lambda or arg needed I think.
God... I think this will make my life easier, code base is shrinking while applying this lesson... thanks... good bye append child.
🎉 1
c

Casey Brooks

08/10/2020, 7:17 PM
Yeah, you’d just pass a generic block, without a receiver. And inner blocks are only for custom functions that themselves can “insert” arbitrary content into themselves. For a paginator, you probably won’t need to accept a
block
lambda, but for, say, a layout function, you’d accept a block and call it in the proper nested place of that layout. The
block
lambda itself should have a receiver of the tag type where it is called, but it’s usually easier to define the function as
inline
so the receiver is more-or-less implicit
And yes, there are a lot of similarly-named things here. ktor-html opens up the usage of the kotlinx.html library (and thus the functions in that package). When used in ktor on the server, the DSL creates a static version of the HTML as the HTTP response. In browser-land, that same DSL is used to actually build browser DOM nodes, instead of writing HTML strings first. Kvision is a separate DSL which happens to look similar to kotlinx.html, but doesn’t actually use it (please correct me if I’m wrong @Robert Jaros 😊).
g

Gunslingor

08/10/2020, 7:26 PM
Yeah, makes sense... my table will need lambda for rows eventually. I think that's right, on the back side jvm delivers raw html via software magic but on js it's all js code... which is a concern... isnt html faster?
Not sure why html should be in the backend either really, definitely should be with the JS
c

Casey Brooks

08/10/2020, 7:29 PM
Yeah, I find it easier/cheaper to push JSON to the client and build the whole frontend in JS, rather than doing server-side templating. Server-side templating may be necessary for certain security reasons though, or for making an app that works even when JS is disabled. And like I mentioned earlier, doing everything on the frontend allows it to possibly be replaced by a vdom to address performance issues with full native JS DOM apis
And I think #kvdom actually does support the kotlinx.html DSL, so should be pretty simple to swap it out. Haven’t actually tried it, though
g

Gunslingor

08/10/2020, 7:32 PM
I agree completely... I just feel strongly the front end should offer a transpiler for kotlin to html OR js... OR css for that matter, but the front is all strings of css and html buried in js... like you say, zero will work if js is disabled.
And I have concerns about performance... definitely going to be slow to load first page
Not sure about after the first, guess it depends
c

Casey Brooks

08/10/2020, 7:34 PM
With
browserProductionWebpack
building and serving a static file, bundles are actually quite small. Not tiny, by any means, but definitely small enough to have reasonable load times now
g

Gunslingor

08/10/2020, 7:39 PM
Yeah, think it can be optimized too... just not sure I buy into this approach. My app will have 40 modules, 1 is a 3d one for three.js.... so statistically speaking the user shouldnt need to download the 3d stuff but 1/40 times... it can be solved with routing on the backend I guess... yeah, I think I can address at least the libs... not included in webpack. Makes sense upon deeper thought... but what if my code gets big, hope webpack gives a way to split... learning still.
c

Casey Brooks

08/10/2020, 7:47 PM
These are the kinds of reasons why I never stuck with frontend dev in the first place 😂 I like being able to do small stuff comfortably in the browser now, but there’s still just such huge hurdles for making large webapps performant that I can’t wrap my head around (and frankly, don’t want to)
g

Gunslingor

08/10/2020, 8:24 PM
lol... well there are always tradeoffs. e.g. server side pagination will load faster when dealing with 1 million records (only 10 records need to be sent at a time for each page) but actual loading of each page will be slower (cause it has to get info remotely).
Really need all options... I like frameworks that have little opinion... I dispise stubbornly opinioned stuff... liferay, django, multiplatform plugin, lol
Ha! rjaros, lol, you own kvision. You should start a github org and move that repo to it, I often skip over personal repos... might get more adoption... if it said jetbrains/kvision I would be using it already. I do want to get into though, time...
r

Robert Jaros

08/10/2020, 8:38 PM
Of course I am 🙂
I thought it was obvious because of github account name 😉
g

Gunslingor

08/10/2020, 8:40 PM
It is better/easy/nicer than what I've been doing? hmmm.... errr... I need to stop hoping frameworks so much, lol, sometimes it's an evolution I guess...
this trick from casey is a huge help... god I was getting tired of typing appendChild. Isn't it horrible the things we write as programmers... I literally once typed "kill all children destroy parents"
😂 1
Any idea what I doing wrong here: is CSS workable like HTML???
Copy code
fun DIV.projectionControlsStyles() {
    CSSBuilder().apply {
        ".toolbar-absolute" {
            position = Position.absolute
            margin = "10px 20px 10px 20px"
            padding = "10px 10px 10px 10px"
            border = "1px solid rgba(0, 0, 0, 0.1)"
            width = LinearDimension.maxContent
        }
    }
}
c

Casey Brooks

08/10/2020, 9:49 PM
I poked as CSS once (like a year ago) and didn’t like it. It felt so much clunkier than just writing normal CSS (much less SCSS). If it had a DSL which allowed nesting selectors, then it might be nicer, but in its current state I don’t think it’s worth it. For adding styles to the HTML DSL, I’d probably just use a style tag with multiline text as normal CSS syntax
g

Gunslingor

08/10/2020, 10:01 PM
Looks like I was doing it write before, has to be applied in init for some reasons... was trying to move em out and into var
Copy code
class Projection {

    val projection = document.create.div("w-100 h-100 p-0") {
        projectionControlsView()
    }

    init {
        val projectionView = ProjectionView()
        projection.appendChild(projectionView.renderer.domElement)
        projection.appendChild(projectionControlsViewStyles)
        ProjectionController(projectionView)
    }
    //TODO: Bug - 3d canvas seems to disappear when window is tool small (F12)
}
Works, clean enough...
image.png
4 Views