Введение в WebAssembly и Kotlin
Современная веб-разработка требует всё большей производительности, особенно для сложных вычислений, обработки изображений, игр и научных приложений. WebAssembly (Wasm) — это низкоуровневый бинарный формат, который выполняется в браузере со скоростью, близкой к нативной. А Kotlin, благодаря своей гибкости и поддержке множества платформ, становится отличным кандидатом для компиляции в Wasm.
В этой статье мы подробно разберём, как настроить проект на Kotlin для компиляции в WebAssembly, напишем практические примеры и рассмотрим лучшие практики оптимизации.
1. Настройка окружения для Kotlin/Native и WebAssembly
Для работы с WebAssembly в Kotlin используется Kotlin/Native — технология, позволяющая компилировать Kotlin-код в нативные бинарные файлы, включая Wasm. Рассмотрим пошаговую настройку.
Установка необходимых инструментов
- IntelliJ IDEA (Community или Ultimate версия) — основная среда разработки.
- Gradle (встроен в IDEA) — система сборки.
- Emscripten SDK — инструмент для работы с Wasm (требуется для некоторых продвинутых сценариев).
Создание проекта
Создайте новый Gradle-проект и добавьте в build.gradle.kts следующую конфигурацию:
plugins {
kotlin("multiplatform") version "1.9.22"
kotlin("plugin.serialization") version "1.9.22"
}
kotlin {
wasm {
binaries.executable()
browser {
commonWebpackConfig {
cssSupport.enabled = true
}
}
}
}
dependencies {
commonMainImplementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
}
Обратите внимание: для Kotlin 1.9+ используется таргет wasm (ранее был wasm32). Убедитесь, что версия Kotlin актуальна.
Структура проекта
После настройки создайте файл src/wasmMain/kotlin/Main.kt — это будет точка входа вашего Wasm-приложения.
2. Написание первого приложения на Kotlin для WebAssembly
Напишем простое приложение, которое вычисляет факториал числа и взаимодействует с JavaScript.
Код на Kotlin
// src/wasmMain/kotlin/Main.kt
import kotlinx.browser.document
import kotlinx.browser.window
fun factorial(n: Int): Long {
var result = 1L
for (i in 1..n) {
result *= i
}
return result
}
fun main() {
window.onload = {
val output = document.getElementById("output") ?: return@onload
val input = document.getElementById("number") ?: return@onload
// Пример вызова
val num = 10
output.textContent = "Факториал $num = ${factorial(num)}"
// Добавляем обработчик для динамического ввода
input.addEventListener("input", {
val value = (input as? org.w3c.dom.HTMLInputElement)?.value?.toIntOrNull() ?: 0
output.textContent = "Факториал $value = ${factorial(value)}"
})
}
}
HTML-шаблон для запуска
Создайте файл src/wasmMain/resources/index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Kotlin Wasm Demo</title>
</head>
<body>
<h1>Калькулятор факториала на Kotlin/Wasm</h1>
<input type="number" id="number" value="10">
<p id="output">Загрузка...</p>
<script src="https://unpkg.com/kotlin-wasm-bridge@latest/dist/kotlin-wasm-bridge.js"></script>
<script src="main.js"></script>
</body>
</html>
3. Взаимодействие Kotlin и JavaScript через WebAssembly
Одна из ключевых возможностей — передача данных между Wasm-модулем и JavaScript. Рассмотрим продвинутые сценарии.
Экспорт функций из Kotlin
Чтобы функция была доступна из JavaScript, её нужно пометить аннотацией @JsExport:
import kotlin.js.JsExport
@JsExport
fun calculatePrimes(limit: Int): String {
val primes = mutableListOf<Int>()
for (i in 2..limit) {
if (primes.none { i % it == 0 }) {
primes.add(i)
}
}
return primes.joinToString(", ")
}
Теперь из JavaScript можно вызвать:
// JavaScript
const result = Kotlin.modules["main"].calculatePrimes(100);
console.log(result); // "2, 3, 5, 7, 11, ..."
Передача сложных объектов
Для передачи JSON-объектов используйте библиотеку kotlinx.serialization:
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
@Serializable
data class User(val name: String, val age: Int)
@JsExport
fun processUserData(json: String): String {
val user = Json.decodeFromString<User>(json)
val processed = User(user.name.uppercase(), user.age + 1)
return Json.encodeToString(processed)
}
4. Оптимизация производительности и лучшие практики
WebAssembly даёт прирост производительности, но важно правильно использовать его возможности.
Избегайте лишних аллокаций
Внутри Wasm-модуля старайтесь использовать примитивные типы и массивы фиксированной длины. Пример:
// Плохо: создание новых объектов в цикле
fun processData(data: List<Int>): List<Int> {
return data.map { it * 2 }
}
// Хорошо: работа с IntArray
fun processDataFast(data: IntArray): IntArray {
val result = IntArray(data.size)
for (i in data.indices) {
result[i] = data[i] * 2
}
return result
}
Минимизируйте взаимодействие с JavaScript
Каждый вызов между Wasm и JS имеет накладные расходы. Старайтесь передавать данные пачками:
// Вместо 1000 отдельных вызовов