エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

Kotlin Fest 2024のためにCompose HTMLでWebサービスを開発した話

こんにちは、モーニーングルーティーン担当、VPoEの河合(@vaaaaanquish)です。 サムネイルの写真は、娘が描いてくれたパパです。上手です。

本記事は、先日開催されましたKotlin Festにおいてエムスリーのスポンサーブースで展開していた『エンジニア トリ診断』の開発秘話を公開するものです。

 

Kotlin Festとエンジニアトリ診断

Kotlin Festは、Kotlinに関する技術カンファレンスで、今年は6月22日に開催されました。 詳しくは参加レポートもありますので、ご参照頂ければ幸いです。

www.m3tech.blog

今回エムスリーはひよこスポンサーで協賛させて頂き、オフライン会場でブースを展開していました。 その中で『エンジニア トリ診断』という、エムスリーが企画し、Kotlin Composeで開発したコンテンツをネタとして提供していました。

当日は、診断結果であるトリのステッカーも含め、多くの方が楽しんでいる様子が伺えて本当にありがたい限りです。 こちらはGitHub Pagesでホスティングされており、まだ以下URLで遊べますので、まだ診断していない方はトリあえずやってみて下さい。

m3dev.github.io

 

Kotlin Compose HTMLとは

トリ診断は、Compose HTMLを利用して開発を進めました。

github.com

Compose HTMLの大枠となっているKotlin Compose Multiplatformは、KotlinとJetpack ComposeをベースにJetBrainsが開発しているUI frameworkです。 Kotlin Compose Multiplatformを使えば、各OSに向けたスマートフォンアプリやWebアプリを共通したKotlinのコードベースで開発出来ます。

Kotlin Composeの中のWeb向けの機能には、Kotlin/WasmをベースとしたCompose Multiplatform for Web、Kotlin/JSをベースとしたCompose HTMLがあります*1

開発開始時点*2で、Kotlin/Wasmは、WebKit非対応でiOSでは動作しませんでした。 以下targetのplatform一覧にも記載されている通りで、実際にExampleを動かした限りでもiPhoneやMac上でCounter等が動作しませんでした。

今回のトリ診断は、Kotlin Fest会場でも遊んで頂きたいため、iOS対応のCompose HTMLを利用し開発を進めました。 私はKotlin開発の経験が多い訳ではありませんが、IntelliJ IDEAをインストールしてすぐ始められて、好調なスタートとなりました。 ちなみに、今回利用したバージョンは次の通りです。

kotlin.version=2.0.0
compose.version=1.6.10

 

Compose HTMLをGitHub Pagesでホスティングする

Compose HTMLなプロジェクトに対してgradlew buildを実行するとbuildディレクトリ配下に、htmlとjsファイルが生成されます。 この時、buildディレクトリの構造はComposeのバージョンによって異なるため注意が必要です。

今回は、./build/dist/js/productionExecutable/に結果が生成されていたのを確認し、GitHub Actionsでgithub-pagesブランチにpush、公開の流れを作成しました。

...
      - name: build
        run: |
          ./gradlew build
      - name: deploy
        uses: JamesIves/github-pages-deploy-action@3.7.1
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          BRANCH: github-pages
          FOLDER: torisetsu/build/dist/js/productionExecutable
          CLEAN: true

あまり先行事例がなかったので、以下に簡素なexampleも公開しています。開発時の参考にして下さい。

github.com

 

OGPを設定する

GitHub Pagesはあくまで静的ファイルホスティングなので、ホスティングしたサービスでSNSシェア時にOGP画像を表示するには、OGP画像ごとにhtmlファイルを作成する必要があります。 今回のトリ診断でも、各結果画面で別々のOGPを表示し、SNSで盛り上がって頂きたい一心で方法を模索しました。

調査の結果、Kotlin側で対応できれば良かったのですが、Compose HTMLではmetatagを上手く扱えない事が分かりました*3

そこで、gradleをテンプレートエンジンとし、htmlを複製するtaskを追加しました。

tasks.register<Copy>("copyStaticPages") {
    listOf(
        "aoitori" to "aoitori",
        "swift_2" to "swift",
        ...
    ).forEach { (fileName, imageName) ->
        from("src/jsMain/resources/base.html") {
            expand(
                mapOf(
                    "ogpImageName" to "$imageName.png"
                )
            )
            rename("base", fileName)
        }
    }
    into("src/jsMain/resources/")
}
tasks["jsProcessResources"].dependsOn("copyStaticPages")

これにより、build時、Compose HTMLによってindex.htmlを生成するタイミングで、各診断結果のページのhtmlも個別に作成されるようになりました。 各ページへのアクセスや挙動については、Compose HTML側でRoutingする実装としました。

 

Kotlinでフロントエンド開発を進める

フロントエンドの多くは、デジスマチームの池奥(@Selria1)が担当しましたので、ここからは池奥の執筆です。

「Kotlinでどうやってマークアップするんだ?」 と思っていたのですが、調べてみると殆どCSSと同じ書き味で記述できることがわかりました。

例として、カードコンポーネントの実装の一部を以下に記載します。 HTMLやJSXとほとんど変わらない記法であることがわかります。(content()はReactでいう childrenです)

Div(
    attrs = {
        style {
            padding(32.px)
            backgroundColor(rgb(255, 255, 255))
            borderRadius(16.px)
            property("box-shadow", "0px 4px 4px rgb(0 0 0 / 0.25), 0px 8px 6px rgb(0 0 0 / 0.10)")
        }
    }
) {
    content()
}

ピクセルの指定は、Intの拡張関数として定義されているため、32.pxと直感的な記述可能です。他にも、rgb関数が用意されていたり、サポートされていないプロパティのために、property(...)という任意のプロパティを設定できる仕組みが用意されていたりと、CSSっぽく読み書きするための工夫が感じられました。

私が所属しているデジスマチームでは、exposedというSQLライブラリを利用しているのですが、exposedでも実際のSQLに近い文法が実現されています。今回の実装を通して、改めてKotlinの文法の自由度の高さを感じました。

ちなみに、当初はstyleにCSSを直書きしていたのですが、レスポンシブ対応のためにスタイル部分は別ファイルに切り出すようにリファクタを行いました。最終的にはCSS Modulesのような、コンポーネントファイルと、そのコンポーネントに対応するスタイルファイルを分離するような形式に落ち着きました。

 

おわりに

本記事では、Compose HTMLによるWeb開発の体験やコツを書きました。

最後に裏話なのですが、トリ診断の元ネタは「Kotlinで何かアイデアない?」とデザイナーに話を振ってみた所、いつの間にか各トリのデザインやステッカーが作られている、という所から始まっています。漫画家デザイナーらの圧倒的パワーを感じるコンテンツが先に出てきていて、Web実装は後追いという形です。最初は「モダンにRemix+honoとか良いよね」と言っていたのですが「デザイナーの熱量を最大限活かすならやっぱ全部Kotlinでしょ!」みたいな会話から、何故か一大プロジェクトかの如くKotlin開発が始まった、という背景だったりします笑

条件分岐はChatGPTと相談しながらガッツリ構築しました

社内にも多く在籍するKotlinユーザも協力してくれて、当日の朝ギリギリまでcommitしてなんとか形になりました。

Kotlin Fest 2024の運営の皆様、当日含め、最高の体験の機会をありがとうございました! Kotlin Festに参加した皆様も、楽しんで頂けたなら幸いです。

 

We are hiring !!

エムスリーは、Kotlinでもどんな言語、フレームワークでも、技術の無駄使いでも、垣根なく技術で盛り上がる会社です。 ギークにこだわりを持って技術に挑む事が、イノベーションに繋がると信じています。

一緒に技術で盛り上がる、ギークな皆さんのチャレンジをお待ちしております。

jobs.m3.com

*1:元々はKotlin/JsベースのものがCompose for Webと呼ばれていたようです。歴史的経緯ですがややこしいので注意が必要です。

*2:2024年6月時点

*3: https://github.com/JetBrains/compose-multiplatform/issues/3172