Kotlin/Wasmに入門してみた
こんにちは!アルダグラムのKANNAの開発お手伝いをさせて頂いている @takjin です。
ここ最近、サーバサイドで Kotlin に触れる時間が増えているのですが、そんな Kotlin から Kotlin/Wasm が昨年からアルファ版として提供されたので入門してみました。
今回は、生成したKotlin/WasmをNext.js経由で動作させるところまで手を動かしてみます。
Kotlin 2.0.0
Next.js 14.2.x
公式で提供されているコードに触れてみる
サンプルコードがGitHubで公開されていますので、手っ取り早く始めるならこちらから。
また、Kotlin Multiplatform Wizardページからもサンプルコードを入手できるようです。
Kotlin/Wasm のサンプルコードの動作確認の手順が紹介されています。
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環境で利用できるようにコンパイルしてくれます。用途によって使い分けると良さそうです
- ブラウザ向けにWasmバイナリをコンパイルします。また
- generateTypeScriptDefinitions()
-
@JsExport
が付与されたKotlinのコードからTyptScriptの型定義ファイル(.d.ts
)を生成してくれます - https://kotlinlang.org/docs/js-ir-compiler.html#preview-generation-of-typescript-declaration-files-d-ts
-
ビルドする
./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です。 世界中のノンデスクワーク業界における現場の生産性アップを実現する現場DXサービス「KANNA」を開発しています。 採用情報はこちら: herp.careers/v1/aldagram0508/
Discussion