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:
- The Kotlin compiler translates your Kotlin code to JavaScript
- It bundles your application with required libraries
- 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:
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
:
<!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
:
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:
./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:
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:
@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:
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:
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.
Using Popular JavaScript Frameworks
Kotlin offers wrappers for popular JavaScript frameworks, making it easier to build web applications.
React with Kotlin
Using the Kotlin React wrappers:
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:
- Compiler Support: Kotlin compiles directly to JavaScript, enabling web development
- Type Safety: Kotlin brings its strong type system to frontend development
- Interoperability: You can easily interoperate with JavaScript libraries and the DOM
- Framework Support: Official wrappers exist for popular frameworks like React
- 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
-
Basic: Create a simple calculator that can add, subtract, multiply, and divide numbers using Kotlin/JS and DOM manipulation.
-
Intermediate: Build a weather app that fetches data from a public weather API and displays it on a webpage.
-
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! :)