Skip to main content

Kotlin JavaScript Interoperability

Introduction

Kotlin is not just for Android or server-side development—it's also a powerful language for web frontend development. Kotlin's JavaScript (JS) interoperability allows you to write Kotlin code that compiles to JavaScript, enabling you to:

  • Create web applications with a type-safe language
  • Share code between frontend and backend
  • Leverage existing JavaScript libraries and frameworks
  • Maintain a consistent language across your entire stack

In this guide, we'll explore how Kotlin works with JavaScript, how to set up a Kotlin/JS project, and how to leverage JS libraries and frameworks in your Kotlin code.

Kotlin to JavaScript Compilation

Kotlin's compiler can target JavaScript instead of JVM bytecode, transforming your Kotlin code into JavaScript that runs in browsers or Node.js environments.

How Kotlin/JS Works

When you compile Kotlin to JavaScript:

  1. The Kotlin compiler translates your Kotlin code to JavaScript
  2. It bundles your application with required libraries
  3. The resulting JavaScript can be integrated into any web project

Setting Up a Kotlin/JS Project

Let's start by creating a simple Kotlin/JS project:

Using Gradle

Create a new build.gradle.kts file with the following configuration:

kotlin
plugins {
kotlin("js") version "1.8.0"
}

repositories {
mavenCentral()
}

kotlin {
js(IR) { // Using the new IR compiler
browser {
webpackTask {
outputFileName = "main.js"
}
}
binaries.executable()
}
}

dependencies {
implementation("org.jetbrains.kotlin-wrappers:kotlin-react:18.2.0-pre.467")
implementation("org.jetbrains.kotlin-wrappers:kotlin-react-dom:18.2.0-pre.467")
}

Project Structure

A basic Kotlin/JS project structure looks like:

my-kotlin-js-project/
├── build.gradle.kts
├── gradle/
├── gradlew
├── gradlew.bat
└── src/
└── main/
├── kotlin/
│ └── App.kt
└── resources/
└── index.html

Your First Kotlin/JS Application

Let's create a simple "Hello World" application:

1. Create index.html

In src/main/resources/index.html:

html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Kotlin JS Demo</title>
</head>
<body>
<div id="root"></div>
<script src="main.js"></script>
</body>
</html>

2. Create App.kt

In src/main/kotlin/App.kt:

kotlin
import kotlinx.browser.document

fun main() {
document.addEventListener("DOMContentLoaded", {
val rootElement = document.getElementById("root")
rootElement?.innerHTML = "<h1>Hello from Kotlin/JS!</h1>"
})
}

3. Build and Run

Run the following command:

bash
./gradlew browserDevelopmentRun

This will:

  • Compile your Kotlin code to JavaScript
  • Start a development server
  • Open your application in a browser

Output in browser: A webpage with "Hello from Kotlin/JS!" displayed as a heading.

Working with JavaScript Libraries

One of the major benefits of Kotlin/JS is its seamless interoperability with existing JavaScript libraries.

Dynamic Type

Kotlin provides the dynamic type, which bypasses the type system when interacting with JavaScript code:

kotlin
external fun require(module: String): dynamic

fun main() {
val lodash = require("lodash")

val array = arrayOf(1, 2, 3, 4, 5)
val evens = lodash.filter(array) { num: Int -> num % 2 == 0 }

console.log(evens) // Outputs: [2, 4]
}

TypeSafe Wrappers

For a type-safe approach, you can create external declarations:

kotlin
@JsModule("lodash")
external object _ {
fun filter(array: Array<Int>, predicate: (Int) -> Boolean): Array<Int>
fun map(array: Array<Int>, transformer: (Int) -> Int): Array<Int>
}

fun main() {
val array = arrayOf(1, 2, 3, 4, 5)
val doubled = _.map(array) { it * 2 }
console.log(doubled.joinToString()) // "2, 4, 6, 8, 10"
}

DOM Manipulation with Kotlin/JS

Kotlin provides convenient wrappers for DOM manipulation through the kotlinx.browser package:

kotlin
import kotlinx.browser.document
import kotlinx.dom.createElement
import org.w3c.dom.HTMLButtonElement

fun main() {
document.addEventListener("DOMContentLoaded", {
val root = document.getElementById("root") ?: return@addEventListener

val button = document.createElement("button") as HTMLButtonElement
button.textContent = "Click me"
button.addEventListener("click", {
button.textContent = "Clicked!"
})

root.appendChild(button)
})
}

Practical Example: A Todo List Application

Let's build a simple Todo List application to showcase Kotlin/JS capabilities:

kotlin
import kotlinx.browser.document
import kotlinx.dom.createElement
import org.w3c.dom.HTMLButtonElement
import org.w3c.dom.HTMLInputElement
import org.w3c.dom.HTMLLIElement

data class TodoItem(val id: Int, val text: String, var completed: Boolean = false)

class TodoApp {
private val todos = mutableListOf<TodoItem>()
private var nextId = 1

fun mount(rootElementId: String) {
val root = document.getElementById(rootElementId) ?: return

// Clear existing content
root.innerHTML = ""

// Create input field and add button
val input = document.createElement("input") as HTMLInputElement
val addButton = document.createElement("button") as HTMLButtonElement
addButton.textContent = "Add"

// Create todo list container
val todoList = document.createElement("ul")

// Add event listener to the button
addButton.addEventListener("click", {
val text = input.value.trim()
if (text.isNotEmpty()) {
addTodo(text)
input.value = ""
renderTodos(todoList)
}
})

// Append elements to the DOM
root.appendChild(input)
root.appendChild(addButton)
root.appendChild(document.createElement("hr"))
root.appendChild(todoList)
}

private fun addTodo(text: String) {
todos.add(TodoItem(nextId++, text))
}

private fun renderTodos(container: org.w3c.dom.Element) {
container.innerHTML = ""

if (todos.isEmpty()) {
container.textContent = "No todos yet. Add one above!"
return
}

todos.forEach { todo ->
val item = document.createElement("li") as HTMLLIElement

// Create checkbox for toggling completion
val checkbox = document.createElement("input")
checkbox.setAttribute("type", "checkbox")
checkbox.checked = todo.completed
checkbox.addEventListener("change", {
todo.completed = checkbox.checked
renderTodos(container)
})

// Create text span
val text = document.createElement("span")
text.textContent = todo.text
if (todo.completed) {
text.style.textDecoration = "line-through"
}

// Create delete button
val deleteBtn = document.createElement("button") as HTMLButtonElement
deleteBtn.textContent = "Delete"
deleteBtn.style.marginLeft = "10px"
deleteBtn.addEventListener("click", {
todos.removeIf { it.id == todo.id }
renderTodos(container)
})

// Append all elements
item.appendChild(checkbox)
item.appendChild(text)
item.appendChild(deleteBtn)
container.appendChild(item)
}
}
}

fun main() {
document.addEventListener("DOMContentLoaded", {
val todoApp = TodoApp()
todoApp.mount("root")
})
}

To see this example in action, compile it with the Gradle configuration we set up earlier and open the HTML file in a browser.

Kotlin offers wrappers for popular JavaScript frameworks, making it easier to build web applications.

React with Kotlin

Using the Kotlin React wrappers:

kotlin
import react.*
import react.dom.*
import kotlinx.browser.document
import kotlinx.html.js.onClickFunction

external interface TodoProps : Props {
var text: String
var completed: Boolean
var onToggle: () -> Unit
var onDelete: () -> Unit
}

val TodoItem = fc<TodoProps> { props ->
li {
input {
attrs {
type = InputType.checkBox
checked = props.completed
onChangeFunction = { props.onToggle() }
}
}

span {
attrs.style = if (props.completed)
js("{ textDecoration: 'line-through' }")
else
js("{}")
+props.text
}

button {
attrs {
onClickFunction = { props.onDelete() }
style = js("{ marginLeft: '10px' }")
}
+"Delete"
}
}
}

val TodoApp = fc<Props> {
val (todos, setTodos) = useState(emptyList<TodoItem>())
val (input, setInput) = useState("")
val nextIdRef = useRef(1)

fun addTodo() {
if (input.isNotBlank()) {
setTodos(todos + TodoItem(nextIdRef.current++, input, false))
setInput("")
}
}

div {
input {
attrs {
value = input
onChangeFunction = { event ->
setInput(event.target.value)
}
}
}

button {
attrs.onClickFunction = { addTodo() }
+"Add"
}

hr {}

if (todos.isEmpty()) {
p { +"No todos yet. Add one above!" }
} else {
ul {
todos.forEachIndexed { index, todo ->
TodoItem {
attrs {
key = todo.id.toString()
text = todo.text
completed = todo.completed
onToggle = {
setTodos(todos.mapIndexed { i, item ->
if (i == index) item.copy(completed = !item.completed)
else item
})
}
onDelete = {
setTodos(todos.filterIndexed { i, _ -> i != index })
}
}
}
}
}
}
}
}

fun main() {
document.getElementById("root")?.let {
render(TodoApp.create(), it)
}
}

Summary

Kotlin/JS provides a powerful way to write web applications using the Kotlin language. Key takeaways include:

  1. Compiler Support: Kotlin compiles directly to JavaScript, enabling web development
  2. Type Safety: Kotlin brings its strong type system to frontend development
  3. Interoperability: You can easily interoperate with JavaScript libraries and the DOM
  4. Framework Support: Official wrappers exist for popular frameworks like React
  5. Code Sharing: You can share code between your backend and frontend when both use Kotlin

By leveraging Kotlin/JS, you get the benefits of a modern, concise, and safe language while still being able to use the entire JavaScript ecosystem.

Additional Resources

Exercises

  1. Basic: Create a simple calculator that can add, subtract, multiply, and divide numbers using Kotlin/JS and DOM manipulation.

  2. Intermediate: Build a weather app that fetches data from a public weather API and displays it on a webpage.

  3. Advanced: Create a Kanban board application using Kotlin and React wrappers that allows creating, editing, and moving tasks between columns.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)