55
45

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

今更htmxに触れてみた

Posted at

htmxとは

htmxは近年話題のJavaScriptライブラリで、JavaScriptのライブラリながらJavaScriptを書くことなく、HTMLだけでAjax通信やそれに伴うDOMの更新を実現することが可能です。

指標として、1年間のGitHubスター数の増加ランキングであるJavaScriptライジングスター 2024のフロントエンドフレームワーク部門ではReactを抑えて1位を獲得しています。

image.png

htmxの思想

htmxの思想については公式サイトに記載されているモチベーションを見るのが分かりやすいでしょう。

motivation

  • Why should only <a> & <form> be able to make HTTP requests?
  • Why should only click & submit events trigger them?
  • Why should only GET & POST methods be available?
  • Why should you only be able to replace the entire screen?

By removing these constraints, htmx completes HTML as a hypertext

↓ Google先生による翻訳版

  • なぜ <a> と <form> だけが HTTP リクエストを実行できるのですか?
  • なぜクリック&送信イベントだけがトリガーする必要があるのですか?
  • なぜ GET & POST メソッドだけを利用可能にすべきなのですか?
  • なぜ画面全体しか交換できないのですか?

これらの制約を取り除くことで、htmx は HTML をハイパーテキストとして完成させます

また、公式サイトには以下のような記述も存在します。

haiku
javascript fatigue:
longing for a hypertext
already in hand

↓ 翻訳版

俳句
JavaScript疲労: すでに手元にある
ハイパーテキストへの憧れ

乱暴な言い方ですが、インタラクティブなWebアプリの実装において、何でもかんでもJavaScriptで実装せずにHTML標準の機能に目を向けて実装しよう!という思想のもとで開発されたライブラリなのかな?という印象を受けました。

また、以下の記事でhtmxの思想について詳しく解説されているので詳しく知りたい方は是非ご覧ください。

特徴

近年主流となっているReactのようなSPAとバックエンドAPIで構成されるWebアプリのようなJSONでデータをやり取りしてJavaScriptでレンダリングする方式とは異なり、htmxではバックエンドからHTMLを返し、フロントエンドではDOMを置換するだけという形式を取っています。

そのため、クライアント側で複雑なロジックを実装する必要がなく、シンプルな実装で完結するいう特徴があります。

htmxではサーバーからのレスポンスで指定されたDOMを書き換えるだけなので、JSON形式でレスポンスを受信した場合、JSONがテキストとしてDOMに反映されます。

実装してみる

実装イメージ

HTMX Tutorial.gif

フロントエンド

フロントエンドの実装はindex.htmlのみで、JavaScriptの実装はしていません。

public/index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>HTMX Tutorial</title>
    <script src="https://unpkg.com/htmx.org@2.0.4"></script>
</head>

<body>
    <h1>Welcome to HTMX Tutorial</h1>
    <h3>This is a simple HTMX template.</h3>
    <button hx-get="/list" hx-target="#content" hx-trigger="click" hx-swap="innerHTML">GET LIST</button>
    <div id="content">
        <div>ボタンをクリックしたらここが更新されます。</div>
    </div>
</body>

<style>
    body {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
    }
</style>
</html>

バックエンド

今回はバックエンドをexpressejsで実装しています。

バックエンドに関してはJSONではなくHTMLでレスポンスを返していることだけ理解すればOKです。

index.js
import express from "express";

const app = express();
const PORT = 3000;

// Serve static files from the "public" directory
app.use(express.static("public"));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.set("view engine", "ejs");
app.engine("ejs", require("ejs").__express);

const list = [
    { id: 1, name: "John Doe", age: 30 },
    { id: 2, name: "Jane Smith", age: 25 },
    { id: 3, name: "Alice Johnson", age: 28 },
    { id: 4, name: "Bob Brown", age: 35 },
    { id: 5, name: "Charlie Davis", age: 22 },
    { id: 6, name: "Diana Prince", age: 27 },
    { id: 7, name: "Ethan Hunt", age: 32 },
    { id: 8, name: "Fiona Apple", age: 29 },
    { id: 9, name: "George Clooney", age: 40 },
    { id: 10, name: "Hannah Montana", age: 21 },
    { id: 11, name: "Ian Somerhalder", age: 33 },
    { id: 12, name: "Jessica Alba", age: 26 },
    { id: 13, name: "Kevin Spacey", age: 38 },
    { id: 14, name: "Liam Neeson", age: 45 },
    { id: 15, name: "Megan Fox", age: 24 },
    { id: 16, name: "Nicolas Cage", age: 50 },
    { id: 17, name: "Olivia Wilde", age: 31 },
    { id: 18, name: "Paul Rudd", age: 36 },
    { id: 19, name: "Quentin Tarantino", age: 55 },
    { id: 20, name: "Rachel McAdams", age: 23 }
];

// Start the server
app.listen(PORT, () => {
    console.log(`Static site is being hosted at http://localhost:${PORT}`);
});

app.get("/list", (req, res) => {
    res.render("list_template", { list, status: 200 });
});
views/list_template.ejs
<div>
    <h3>ユーザ一覧</h3>
</div>
<table>
    <thead>
        <tr>
            <th>id</th>
            <th>name</th>
            <th>age</th>
        </tr>
    </thead>
    <tbody>
        <% list.forEach(function(item) { %>
            <tr>
                <td>
                    <%= item.id %>
                </td>
                <td>
                    <%= item.name %>
                </td>
                <td>
                    <%= item.age %>
                </td>
            </tr>
            <% }) %>
    </tbody>
</table>

解説

htmxではHTML要素に独自のhx-xxxxx属性を指定して各種処理の設定を行います。

下記サンプルでは以下のような設定をしています。

  1. ボタンをクリックしたら
  2. /listへGETリクエストを送信し
  3. レスポンスの内容で#content配下の要素を置き換える
<button  
    hx-trigger="click" 
    hx-get="/list" 
    hx-target="#content" 
    hx-swap="innerHTML">
    GET LIST
</button>
hx-trigger

Ajaxリクエストをトリガーする操作を指定します。
clicksubmitmouseoverといった操作のほか、load(初回読み込み時に発火)やrevealed(viewPortに読み込まれたタイミングで発火)といった特殊なトリガーが存在しています。

hx-get

指定したURLに対してGETリクエストを送信する命令を指定します。
その他にもhx-posthx-puthx-deleteといった形式でHTTPリクエストを指定することが可能です。

hx-target

レスポンスの内容で置換するhtml要素をCSSセレクタで指定します。

hx-swap

hx-targetで指定された要素をレスポンスの値で置換する際の設定です。
上記例ではinnerHTMLを指定しており、#contentの内部を置換しています。

<!-- before -->
<div id="content">
    <div>ボタンをクリックしたらここが更新されます。</div>
</div>

<!-- after -->
<div id="content">
    <table>...</table>
</div>

おわりに

今回htmxに入門してみた感想ですが、日本ではまだ流行っていないのか、日本語の記事が少なかったですね。
公式サイトも英語のみなのでしばらくは翻訳機能と仲良くする必要がありそうです。

実装面の話であれば、基本となるのが以下のステップなので学習コストはかなり低い印象を受けました。

  1. 特定の動作に紐づけて(hx-trigger)
  2. HTTPリクエストを実行して(hx-get, hx-post)
  3. 指定した要素をレスポンスの内容で置換する(hx-target)

シンプルなWebサービスであればフロントエンドはhtmxだけで事足りると思うので今後の動向に注目しつつ、htmxで色々遊んでいきたいところです。

参考

55
45
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
55
45

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?