-
-
Notifications
You must be signed in to change notification settings - Fork 343
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add shared JVM/JS Kotlin code example
- Loading branch information
Showing
8 changed files
with
863 additions
and
0 deletions.
There are no files selected for viewing
118 changes: 118 additions & 0 deletions
118
example/kotlinlib/web/5-webapp-kotlinjs-shared/build.mill
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package build | ||
import mill._, kotlinlib._, kotlinlib.js._ | ||
|
||
trait AppKotlinModule extends KotlinModule { | ||
def kotlinVersion = "1.9.25" | ||
} | ||
|
||
trait AppKotlinJSModule extends AppKotlinModule with KotlinJSModule | ||
|
||
object `package` extends RootModule with AppKotlinModule { | ||
|
||
def ktorVersion = "2.3.12" | ||
def kotlinHtmlVersion = "0.11.0" | ||
def kotlinxSerializationVersion = "1.6.3" | ||
|
||
def mainClass = Some("webapp.WebApp") | ||
|
||
def moduleDeps = Seq(shared.jvm) | ||
|
||
def ivyDeps = Agg( | ||
ivy"io.ktor:ktor-server-core-jvm:$ktorVersion", | ||
ivy"io.ktor:ktor-server-netty-jvm:$ktorVersion", | ||
ivy"io.ktor:ktor-server-html-builder-jvm:$ktorVersion", | ||
ivy"io.ktor:ktor-server-content-negotiation-jvm:$ktorVersion", | ||
ivy"io.ktor:ktor-serialization-kotlinx-json-jvm:$ktorVersion", | ||
ivy"ch.qos.logback:logback-classic:1.5.8", | ||
) | ||
|
||
def resources = Task { | ||
os.makeDir(Task.dest / "webapp") | ||
val jsPath = client.linkBinary().classes.path | ||
os.copy(jsPath / "client.js", Task.dest / "webapp/client.js") | ||
os.copy(jsPath / "client.js.map", Task.dest / "webapp/client.js.map") | ||
super.resources() ++ Seq(PathRef(Task.dest)) | ||
} | ||
|
||
object test extends KotlinTests with TestModule.Junit5 { | ||
def ivyDeps = super.ivyDeps() ++ Agg( | ||
ivy"io.kotest:kotest-runner-junit5-jvm:5.9.1", | ||
ivy"io.ktor:ktor-server-test-host-jvm:$ktorVersion" | ||
) | ||
} | ||
|
||
object shared extends Module { | ||
|
||
trait SharedModule extends AppKotlinModule with PlatformKotlinModule { | ||
def processors = Task { | ||
defaultResolver().resolveDeps( | ||
Agg( | ||
ivy"org.jetbrains.kotlin:kotlin-serialization-compiler-plugin:${kotlinVersion()}" | ||
) | ||
) | ||
} | ||
|
||
def kotlincOptions = super.kotlincOptions() ++ Seq( | ||
s"-Xplugin=${processors().head.path}" | ||
) | ||
} | ||
|
||
object jvm extends SharedModule { | ||
def ivyDeps = super.ivyDeps() ++ Agg( | ||
ivy"org.jetbrains.kotlinx:kotlinx-html-jvm:$kotlinHtmlVersion", | ||
ivy"org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion", | ||
) | ||
} | ||
object js extends SharedModule with AppKotlinJSModule { | ||
def ivyDeps = super.ivyDeps() ++ Agg( | ||
ivy"org.jetbrains.kotlinx:kotlinx-html-js:$kotlinHtmlVersion", | ||
ivy"org.jetbrains.kotlinx:kotlinx-serialization-json-js:$kotlinxSerializationVersion", | ||
) | ||
} | ||
} | ||
|
||
object client extends AppKotlinJSModule { | ||
def splitPerModule = false | ||
def moduleDeps = Seq(shared.js) | ||
def ivyDeps = Agg( | ||
ivy"org.jetbrains.kotlinx:kotlinx-html-js:$kotlinHtmlVersion", | ||
ivy"org.jetbrains.kotlinx:kotlinx-serialization-json-js:$kotlinxSerializationVersion", | ||
) | ||
} | ||
} | ||
|
||
// A Kotlin/JVM backend server wired up with a Kotlin/JS front-end, with a | ||
// `shared` module containing code that is used in both client and server. | ||
// Rather than the server sending HTML for the initial page load and HTML for | ||
// page updates, it sends HTML for the initial load and JSON for page updates | ||
// which is then rendered into HTML on the client. | ||
// | ||
// The JSON serialization logic and HTML generation logic in the `shared` module | ||
// is shared between client and server, and uses libraries like `kotlinx-serialization` and | ||
// `kotlinx-html` which work on both Kotlin/JVM and Kotlin/JS. This allows us to freely | ||
// move code between the client and server, without worrying about what | ||
// platform or language the code was originally implemented in. | ||
// | ||
// This is a minimal example of shared code compiled to Kotlin/JVM and Kotlin/JS, | ||
// running on both client and server, meant for illustrating the build | ||
// configuration. A full exploration of client-server code sharing techniques | ||
// is beyond the scope of this example. | ||
|
||
/** Usage | ||
|
||
> ./mill test | ||
...webapp.WebAppTestssimpleRequest ... | ||
|
||
> ./mill runBackground | ||
|
||
> curl http://localhost:8083 | ||
...What needs to be done... | ||
... | ||
|
||
> curl http://localhost:8083/static/client.js | ||
...kotlin.js... | ||
... | ||
|
||
> ./mill clean runBackground | ||
|
||
*/ |
89 changes: 89 additions & 0 deletions
89
example/kotlinlib/web/5-webapp-kotlinjs-shared/client/src/ClientApp.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
package client | ||
|
||
import kotlinx.browser.document | ||
import kotlinx.browser.window | ||
import kotlinx.html.div | ||
import kotlinx.html.stream.createHTML | ||
import kotlinx.serialization.* | ||
import kotlinx.serialization.json.* | ||
import org.w3c.dom.Element | ||
import org.w3c.dom.HTMLInputElement | ||
import org.w3c.dom.asList | ||
import org.w3c.dom.events.KeyboardEvent | ||
import org.w3c.dom.get | ||
import org.w3c.fetch.RequestInit | ||
import shared.* | ||
|
||
object ClientApp { | ||
|
||
private var state = "all" | ||
|
||
private val todoApp: Element | ||
get() = checkNotNull(document.getElementsByClassName("todoApp")[0]) | ||
|
||
private fun postFetchUpdate(url: String) { | ||
window | ||
.fetch(url, RequestInit(method = "POST")) | ||
.then { it.text() } | ||
.then { text -> | ||
todoApp.innerHTML = createHTML().div { | ||
renderBody(Json.decodeFromString<List<Todo>>(text), state) | ||
} | ||
initListeners() | ||
} | ||
} | ||
|
||
private fun bindEvent(cls: String, url: String, endState: String? = null) { | ||
document.getElementsByClassName(cls)[0] | ||
?.addEventListener("mousedown", { | ||
postFetchUpdate(url) | ||
if (endState != null) state = endState | ||
} | ||
) | ||
} | ||
|
||
private fun bindIndexedEvent(cls: String, block: (String) -> String) { | ||
for (elem in document.getElementsByClassName(cls).asList()) { | ||
elem.addEventListener( | ||
"mousedown", | ||
{ postFetchUpdate(block(elem.getAttribute("data-todo-index")!!)) } | ||
) | ||
} | ||
} | ||
|
||
fun initListeners() { | ||
bindIndexedEvent("destroy") { | ||
"/delete/$state/$it" | ||
} | ||
bindIndexedEvent("toggle") { | ||
"/toggle/$state/$it" | ||
} | ||
bindEvent("toggle-all", "/toggle-all/$state") | ||
bindEvent("todo-all", "/list/all", "all") | ||
bindEvent("todo-active", "/list/active", "active") | ||
bindEvent("todo-completed", "/list/completed", "completed") | ||
bindEvent("clear-completed", "/clear-completed/$state") | ||
|
||
val newTodoInput = document.getElementsByClassName("new-todo")[0] as HTMLInputElement | ||
newTodoInput.addEventListener( | ||
"keydown", | ||
{ | ||
check(it is KeyboardEvent) | ||
if (it.keyCode == 13) { | ||
window | ||
.fetch("/add/$state", RequestInit(method = "POST", body = newTodoInput.value)) | ||
.then { it.text() } | ||
.then { text -> | ||
newTodoInput.value = "" | ||
todoApp.innerHTML = createHTML().div { | ||
renderBody(Json.decodeFromString<List<Todo>>(text), state) | ||
} | ||
initListeners() | ||
} | ||
} | ||
} | ||
) | ||
} | ||
} | ||
|
||
fun main(args: Array<String>) = ClientApp.initListeners() |
11 changes: 11 additions & 0 deletions
11
example/kotlinlib/web/5-webapp-kotlinjs-shared/resources/logback.xml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<configuration> | ||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||
<encoder> | ||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||
</encoder> | ||
</appender> | ||
<root level="error"> | ||
<appender-ref ref="STDOUT"/> | ||
</root> | ||
<logger name="io.netty" level="ERROR"/> | ||
</configuration> |
Oops, something went wrong.