ローソンデジタルイノベーション テックブログ

ローソンデジタルイノベーション(LDI)の技術ブログです

HTMX 入門しました

ご無沙汰しております。LDIで開発担当しているOKです。

今回のテーマですが、
普段はWebのフロントエンドとか全然やってないのですが、
訳あってHTMXを使ってみたので、アウトプットも兼ねてまずは導入部分について記事にしようと思います。

HTMXとは

公式ドキュメント には次のようにあります。

htmx gives you access to AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext

(訳) htmxは、属性を使用して、AJAX、CSSトランジション、WebSocket、およびサーバー送信イベントに直接HTMLでアクセスできるようにするため、ハイパーテキストのシンプルさとパワーで最新のユーザーインターフェイスを構築できます。

ということですが、JavaScriptを書かずとも、通常のHTML記法を拡張した形で通信したり、 DOMレベルでの更新ができたりするライブラリで、 割とシンプルな記述でもSPA的なものが作れます。

とりあず今回の完成系

今回どんなことをやるかという事ですが、、大したことはしてないです。
ボタンを押すとアンカーに、アンカーを押すとボタンに変わるってだけ!!

見てわかる通り(わからないか、、、)ページ全体をリロードしているわけではなく、 コンポーネントが部分的に差し代わっています。
右側の通信結果のレスポンスでも部分的なコンテンツしか返ってきてない事がわかるかと思います。

HTMX参考1

それでは、これを作っていきます。

開発環境情報

  • Mac Book Pro
  • HTMX (v2.0.4)
  • Go言語 (v1.23.2)
    • Gin (v1.10)

それと、とりあえずレイアウトを考えるのが面倒なので、今回はclassとか指定しなくてもそれっぽいレイアウトに整えてくれる MVP.cssを利用してます。

ディレクトリ構成

- main.go
- assets/
  - css
    - mvp.css
- templates/
  - footer.html
  - header.html
  - sample1.html
  - sample2.html
  - index.html   
- go.mod

実装

まず、HTMLのテンプレートはそれぞれの実装は次の通り。

header.html

定型的になる記述になる部分を header として定義しました。
8〜10行目で HTMX を指定しています。今回は簡単に利用するためCDNを参照していますが、本番環境では非推奨とのことです
(メニューの内容は今回触れないです。また機会があれば)

{{define "header.html"}}
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="x-ua-compatible" content="IE=edge" >
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="/assets/css/mvp.css">
    <script src="https://unpkg.com/htmx.org@2.0.4" 
      integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" 
      crossorigin="anonymous"></script>
</head>
<body>
<header>
    <nav>
        <ul>
            <li><a href="">編集画面</a></li>
            <li><a href="">無限スクロール</a></li>
        </ul>
    </nav>
</header>
{{end}}

footer.html

こちらも定型的なところで footer としています。
特に説明することは何もないですね。

{{define "footer.html"}}
<footer>
    <p>Copyright example.com All Rights Reserved.</p>
</footer>
</body>
</html>
{{end}}

sample1.html

ボタンが表示されていて、そのボタンを押すとアンカーに切り替えるコンポーネントです。
4行目のbuttonのところにHTMXの処理を記述しています。
とりあえずコードを全部載せちゃいたいので、説明は後述します。

{{define "sample1.html"}}
<main>
    <section>
        <button hx-get="/sample2" hx-target="main" hx-swap="outerHTML">ボタンからアンカー</button>
    </section>
</main>
{{end}}

sample2.html

こちらは、アンカー表示されていて、それを押すとボタンに切り替えるコンポーネントです。 こちらも詳細は後述します。

{{define "sample2.html"}}
<main>
    <section>
        <a hx-get="/sample1" hx-target="main" hx-swap="outerHTML">アンカーからボタン</a>
    </section>
</main>
{{end}}

index.html

あとはトップページですが、header、footer、sample1のコンポーネントを組み合わせて構成しただけです。

{{template "header.html" .}}
{{template "sample1.html" .}}
{{template "footer.html" .}}

main.go

最後にこれらを配信するサーバの実装です。
Goでサクッと作成。

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()

    // HTMLテンプレートなど設定
    router.Static("/assets", "./assets")
    router.LoadHTMLGlob("./templates/*.html")

    setupAPI(router)

    // サーバ起動
    router.Run(":8080")
}

// いろいろ追加していくつもりなので、APIのセットアップは別メソッド
func setupAPI(router *gin.Engine) {
    router.GET("/", func(context *gin.Context) {
        context.HTML(http.StatusOK, "index.html", gin.H{})
    })
    router.GET("/sample1", func(context *gin.Context) {
        context.HTML(http.StatusOK, "sample1.html", gin.H{})
    })
    router.GET("/sample2", func(context *gin.Context) {
        context.HTML(http.StatusOK, "sample2.html", gin.H{})
    })
}

HTMX の動作解説

それでは、 sample1.html ベースに解説していきます。

<main>
    <section>
        <button hx-get="/sample2" hx-target="main" hx-swap="outerHTML">ボタンからアンカー</button>
    </section>
</main>

hx-get ですが、これは AJAXのGETリクエストを実施してくれます。今回だと /sample2 に対してGETリクエストをしてくれる形です。
他にも hx-posthx-puthx-delete などもあり、名前でわかると思いますが、それぞれで POSTやPUT、DELETEリクエストを出す事もできます。

このリクエストを受けるサーバ側はというと、 sample2.html を返すようになっているので、

router.GET("/sample2", func(context *gin.Context) {
    context.HTML(http.StatusOK, "sample2.html", gin.H{})
})

これがレスポンスとして返ってきます。

<main>
    <section>
        <a hx-get="/sample1" hx-target="main" hx-swap="outerHTML">アンカーからボタン</a>
    </section>
</main>

この返ってきたレスポンスをどう扱うかを操作しているのが hx-targethx-swap になります。

hx-targetmain を指定しているので、 mainのタグが対象になります。
hx-swap で指定している outerHTMLターゲットに指定した要素全体をレスポンスで置き換える ものになります。
この結果、mainタグで囲んでいる全体が置き換わるという動きをしてくれることになります。


ざっくりとHTMXの導入部分に関して今回は説明させてもらいました。
次回はもう少し実用的な所に踏み込んでいきたいと思うので乞うご期待ください。

ではまた!!