Jetpack Compose for Web をやってみた

2021.05.07

はじめに

JetPack Compose で Web 対応がでてきましたね。

マルチプラットフォーム系だと他にも Flutter、 React Native などがあり、バチバチの覇権争いが起こっていて、まだまだどれも発展途上で破壊的な変更やパラダイムシフトも多いとは思いますが面白い時期だと思います。

Jetpack Compose for Web

Kotlin Multiplatform の最大の難所は Gradle の設定だと思う。Web のみで素朴に Run できて Build できてから、Multiplatform 部分に挑戦したほうが良いと思います。

あまり Gradle の設定部分でハマりたくないので、本記事ではチュートリアルのままでやります。

このチュートリアルでの完成品はこれになります。ハマったらこちらを参考にしよう。

プロジェクト作成

Intellj IDEA からプロジェクト作成します。

GradleプロジェクトからKotlin/Multiplatformを選びます。罠としてはKotlinの項目からKotlin/Multiplatformを選ぶとGradle力がない人は詰みます。KMMおいては、下手にステップしない。まず素朴に背伸びしない。

チュートリアルでは Gradle を Kotlin DSL で選ぶようになっています。kts での情報がまだまだ少ないのではまりやすいかもしれないですが、頑張ろう。

settings.gradle.kts の設定

./settings.gradle.kts

rootProject.name = "jetpack-compose-web"

pluginManagement {
    repositories {
        gradlePluginPortal()
        maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
    }
}

注意としては、rootProject.nameは消さずに追記します。

AndroidのGradleの設定をしていると、settings.gradle.ktsにrepositoriesの設定をするんだっと言った感じ。 そして俺たちは Gradle を雰囲気で設定している。

build.gradle.kts の設定

./build.gradle.kts

// Add compose gradle plugin
plugins {
    kotlin("multiplatform") version "1.4.32"
    id("org.jetbrains.compose") version "0.0.0-web-dev-11"
}

// Add maven repositories
repositories {
    mavenCentral()
    maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}

// Enable JS(IR) target and add dependencies
kotlin {
    js(IR) {
        browser()
        binaries.executable()
    }
    sourceSets {
        val jsMain by getting {
            dependencies {
                implementation(compose.web.web)
                implementation(compose.runtime)
            }
        }
    }
}

下手に差分を追記しようとしない。ここは全部上のに入れ替える。全コピペ。

また背伸びしてKotlin 1.5.0にした君!またやっちゃったね。のちにエラーになるけど、1.4.32じゅないと動かないよ。

This version (1.0.0-beta06) of the Compose Compiler requires Kotlin version 1.4.32 but you appear to be using Kotlin version 1.5.0 which is not known to be compatible.

ソースコードとリソースのフォルダを作る

以下のフォルダを作成する。srcすら最初ないので、すべて自分で作成する。

  • src/jsMain/kotlin
  • src/jsMain/resources

index.htmlを作成する

src/jsMain/resources/index.html

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

<script src="REPLACE_WITH_YOUR_MODULE_NAME.js"></script> ここ何を設定したらええの?ってなるけど、たぶんだけど、./settings.gradle.ktsで設定したrootProject.namejetpack-compose-webを設定するんだと思う。

そして俺たちは Gradle を雰囲気で...(ry

Main.ktを作成する。

index.html<div id="root"></div> の部分をJSでレンダーする部分をMain.ktで記述する。

チュートリアルではimportの記述がないのでハマる。完成品のほうみて頑張る。 みんな大好きな保管がきかないので、importが alt enter のauto fixができなくて死ぬ。

import androidx.compose.runtime.mutableStateOf
import androidx.compose.web.css.padding
import androidx.compose.web.css.px
import androidx.compose.web.elements.Button
import androidx.compose.web.elements.Div
import androidx.compose.web.elements.Span
import androidx.compose.web.elements.Text
import androidx.compose.web.renderComposable

fun main() {
    val count = mutableStateOf(0)

    renderComposable(rootElementId = "root") {
        Div(style = { padding(25.px) }) {
            Button(attrs = {
                onClick { count.value = count.value - 1 }
            }) {
                Text("-")
            }

            Span(style = { padding(15.px) }) {
                Text("${count.value}")
            }


            Button(attrs = {
                onClick { count.value = count.value + 1 }
            }) {
                Text("+")
            }
        }
    }
}

補足: コード補完を使うために

Gradle のSyncをいくらしてもソースコードのindexが走らないので、これが最小手順がかわらないが以下の通り。

そして俺たちは Gradle を雰囲気で...(ry

動かなくてよいので、Runのコマンドを叩きライブラリとかをダウンロードさせる

$ ./gradlew jsBrowserRun

src/jsMain/kotlinをソースコードのフォルダだとIDEに認識させる

IDE再起動する

indexが走って保管が効くようになるはず。importをalt enterで追加してくれます。

ブラウザで実行する

$ ./gradlew jsBrowserRun

http://localhost:8080/ でアクセスできるようになる。

公開用にビルドする

$ ./gradlew jsBrowserProductionWebpack

ビルドの成果物はbuild/distributionsに置かれる。

build/distributions
├── index.html
├── jetpack-compose-web.js
└── jetpack-compose-web.js.map

補足: ポート番号かえたい!

ビルドでWebpackを使っているぽい。jsBrowserRunではwebpack-dev-serverを使っているのかな。

port番号を8080から他のポート(ex:3000)に変えたい場合は以下のようにする

./build.gradle.kts

...
kotlin {
    js(IR) {
        browser {
            runTask {
                devServer = devServer?.copy(port = 3000)
            }
        }
        binaries.executable()
    }
    ...
}

まとめ

出たてでまだまだハマりどころがあるけど、面白いね。各マルチプラットフォームの戦略も違うので楽しいと思う。最近Reactばっかりだけど、元AndroiderとしてKMMも定点観測していこう。