🕌

Kotlin/Wasmに入門してみた

2024/11/14に公開

こんにちは!アルダグラムのKANNAの開発お手伝いをさせて頂いている @takjin です。

ここ最近、サーバサイドで Kotlin に触れる時間が増えているのですが、そんな Kotlin から Kotlin/Wasm が昨年からアルファ版として提供されたので入門してみました。

今回は、生成したKotlin/WasmをNext.js経由で動作させるところまで手を動かしてみます。

Kotlin 2.0.0
Next.js 14.2.x

公式で提供されているコードに触れてみる

サンプルコードがGitHubで公開されていますので、手っ取り早く始めるならこちらから。
https://github.com/Kotlin/kotlin-wasm-examples

また、Kotlin Multiplatform Wizardページからもサンプルコードを入手できるようです。
https://kmp.jetbrains.com/

Kotlin/Wasm のサンプルコードの動作確認の手順が紹介されています。
https://kotlinlang.org/docs/wasm-get-started.html#before-you-start

Kotlin/Wasm事始め

事前準備

まず、Kotlinプロジェクトを新規に作成します。(IntelliJ経由で作成しています
build には gradle を利用していきます。
プロジェクト名は何でも良いですが、ここでは kotlin-wasm としておきます。

作成したら main ディレクトリを wasmJsMain にリネームします。
リネームしないと、ビルドタスクを実行しても .wasm ファイルが生成されません。

Main.kt にメソッドを一つ追加しておきます。

// Main.kt
@JsExport
fun greet(): String {
    return "Hello this is Kotlin/Wasm!"
}

build.gradle.kts

Main.kt にメソッドを追加したら build.gradle.kts を編集します。.wasm を生成するミニマルな構成になります。

plugins {
    kotlin("multiplatform") version "2.0.0"
}

repositories {
    mavenCentral()
}

kotlin {
    wasmJs {
        binaries.executable()
        browser()
        generateTypeScriptDefinitions()
    }
}
  • binaries.executable()
    • Wasmのバイナリを実行可能な形式でビルドすることを指定します
  • browser()
    • ブラウザ向けにWasmバイナリをコンパイルします。また nodejs() を指定すると、Node.js環境で利用できるようにコンパイルしてくれます。用途によって使い分けると良さそうです
  • generateTypeScriptDefinitions()

ビルドする

./gradlew wasmJsNodeRun でビルドすると、プロジェクト内に build ディレクトリが生成されます。build/js/packages/ の中を辿っていくと、.wasm .mjs .d.ts ファイルなどが生成されているの確認できます。

build/js/packages/kotlin-wasm-wasm-js/kotlin
├── kotlin-wasm-wasm-js.d.ts
├── kotlin-wasm-wasm-js.mjs
├── kotlin-wasm-wasm-js.uninstantiated.mjs
├── kotlin-wasm-wasm-js.wasm
└── kotlin-wasm-wasm-js.wasm.map

Next.jsでKotlin/Wasmを動作させてみる

先ほど生成された.wasm .mjs .d.ts ファイルを任意のディレクトリに格納します(ここでは src/resources/kotlin-wasm/ ディレクトリ直下に入れます。

ここで一手間必要です。api/kotlin-wasm/routes.tsを作成し、
kotlin-wasm-wasm-js.uninstantiated.mjsのwasmファイルを読み込んでいるパスを /api/kotlin-wasm に書き換えます。

// kotlin-wasm-wasm-js.uninstantiated.mjs
- const wasmFilePath = './kotlin-wasm-wasm-js.wasm';
+ const wasmFilePath = '/api/kotlin-wasm';
// api/kotlin-wasm/route.ts
import fs from 'fs'
import { NextResponse } from 'next/server'

export async function GET() {
  const wasmPath = `${process.cwd()}/src/resources/kotlin-wasm/kotlin-wasm-wasm-js.wasm')`
  const wasm = fs.readFileSync(wasmPath) 
  return new NextResponse(wasm, {
    headers: { 'Content-Type': 'application/wasm' },
  }) 
}

なぜ、こんな回り道をしているのかというと、生成された .mjs 経由で読み込む際にこのままだとNot Foundエラーになるからです。生成された kotlin-wasm-wasm-js.uninstantiated.mjs の中を見てみると fetch() 経由で取得している箇所は下記のようになっていて、fetchする際にpublicフォルダにあたる http://localhost:3000/kotlin-wasm-wasm-js.wasm にアクセスしてしまうからです。(※あまり筋が良いとは言えないのですが、実験的な機能をローカル環境で動作確認したい、ということに主眼を置いているので大目に見ていただければと

// kotlin-wasm-wasm-js.uninstantiated.mjs
// 該当箇所を抜粋したもの
const wasmFilePath = './kotlin-wasm-wasm-js.wasm';

// fetchする際にpublicフォルダにあたる http://localhost:3000/kotlin-wasm-wasm-js.wasm にアクセスしてしまう
wasmInstance = (await WebAssembly.instantiateStreaming(fetch(wasmFilePath), importObject)).instance;

src/resources/kotlin-wasm/ に格納したwasmファイルを読み込むhookを用意します。

// useWasm.ts
export const useWasm = () => {
  const [wasm, setWasm] = useState<typeof import('../../resources/kotlin-wasm/kotlin-wasm-wasm-js') | undefined>()
  const [isPending, startTransition] = useTransition()

  useEffect(() => {
    startTransition(async () => {
      const loadWasm = await import('../../resources/kotlin-wasm/kotlin-wasm-wasm-js')
      setWasm(loadWasm)
    })
  }, [])

  return {
    isPending,
    wasm
  }
}

page側で useWasm を使って表示してみます。

// page.tsx
"use client"

import { Box } from "@mui/material"
import { useWasm } from './hooks/useWasm'

const ExamplePage = () => {
  const { isPending, wasm } = useWasm()
  return (
    <Box>
      {isPending ? 'Loading...' : wasm?.greet()}
    </Box>
  )
}

export default ExamplePage

npx next devで起動して localhost:3000 にアクセスして Hello this is Kotlin/Wasm! と表示されていれば成功です。

普段、なかなか触れることのない技術要素に触れるといい刺激になりますね。
まだまだ入門したばかりなので、これを機にKotlin/Wasmに少しづつ触れていきたいと思います。

もっとアルダグラムエンジニア組織を知りたい人、ぜひ下記の情報をチェックしてみてください!

アルダグラム Tech Blog

Discussion