🧪

WordPressでHtmxを使ってみた

2024/06/21に公開

この記事はオンライン勉強会「htmx勉強会配信 - connpass」用に突貫で書いたので誤字脱字間違いが沢山ある気がします。すみません。ご指摘お待ちしています。

今日のゴール

WordPressでHtmxを使ったサンプルを書いて実行してみます。

教材

https://wpshout.com/htmx-and-wordpress/
を有り難く使わせてもらいました。

STEP1 まずは教材をそのまま試してみよう

git clone https://gitlab.com/i922/htmx-live-search.git
cd htmx-live-search
npx @wp-now/wp-now start

プラグインが有効化されて、「HTML Live Search」というブロックが利用できるようになるので、適当なページで呼び出して実行してみます。

STEP2 教材のコードを読む

いっぱいファイルあるけどキモのコードは htmx-live-search.phpajax-functions.php に全部書かれています。

htmx-live-search.php(抜粋)

<input type="search" name="search" placeholder="Search..."
            hx-post="<?php echo admin_url('admin-ajax.php'); ?>?action=live_search" hx-target="#search-results"
            hx-trigger="input changed delay:500ms, search" hx-indicator=".htmx-indicator">

        <span class="htmx-indicator" style='display:inline;'>Searching…</span>

ajax-functions.php
https://gitlab.com/i922/htmx-live-search/-/blob/main/ajax-functions.php

これです。

STEP3 WordPressのAjaxの扱い方を知る

知ってたら読み飛ばしOK

公式はこちら
https://developer.wordpress.org/plugins/javascript/ajax/

個人ブログでははにわまんさんの記事が良さそう
https://haniwaman.com/wordpress-ajax/

つまりadmin-ajax.php にGETやPOSTでゴニョゴニョすることでajaxを実現できます(雑ですみません、詳細は公式読みましょう)。

hx-post="<?php echo admin_url('admin-ajax.php'); ?>?action=live_search"

ここですね、今回はlive_searchという名前をactionで渡していて、その本体は ajax-functions.php に書かれています。

$search_term = $_POST['search'];そのまま使ってるのはちょっと怖いし色々雑いので、このプラグインはあくまでお試しであることを念頭においてください(記事にもそう書いてあります)

STEP4 教材を参考にちょっとカスタムしたコードを書いてみよう

強引ですが、デフォルトの検索ブロックをオーバーライドして同じような実装できたら面白いかもしれないと思ってちょっと書いてみました。


// Htmxのcdnを読み込み
function htmx_add_htmx_js()
{
    // Enqueue HTMX script
    wp_enqueue_script('htmx-script', 'https://unpkg.com/htmx.org@2.0.0/dist/htmx.min.js', array(), '2.0.0', true);

}
add_action('init', 'htmx_add_htmx_js');
// CSSを読み込み
function htmx_enqueue_styles()
{
    if (has_block('core/search')) {
        wp_enqueue_style('search-block-style', plugin_dir_url(__FILE__) . 'style.css', array(), '1.0.0');
    }
}
add_action('enqueue_block_assets', 'htmx_enqueue_styles');

// 検索ブロックにフロント側だけhtmxの属性等を追加する
function add_htmx_attributes_and_elements_to_search_input($block_content, $block)
{
    // 検索ブロックかどうかを確認
    if ($block['blockName'] === 'core/search') {
        // DOMDocumentのインスタンスを作成
        $dom = new DOMDocument();
        // HTMLを読み込む
        @$dom->loadHTML(mb_convert_encoding($block_content, 'HTML-ENTITIES', 'UTF-8'));

        // inputタグを取得
        $inputs = $dom->getElementsByTagName('input');

        foreach ($inputs as $input) {
            // inputタグのクラス名をチェックしてターゲットを絞り込む
            if ($input->getAttribute('class') === 'wp-block-search__input') {
                $ajax_url = admin_url('admin-ajax.php') . '?action=live_search';
                // HTMX属性を追加
                $input->setAttribute('hx-post', $ajax_url);
                $input->setAttribute('hx-trigger', 'input changed delay:500ms, search');
                $input->setAttribute('hx-target', '#search-results');
                $input->setAttribute('hx-indicator', '.htmx-indicator');

                // spanタグを作成し、inputタグの後に追加
                $span = $dom->createElement('span', 'Searching…');
                $span->setAttribute('class', 'htmx-indicator');
                $span->setAttribute('style', 'display:inline;');
                $input->parentNode->insertBefore($span, $input->nextSibling);

                // divタグを作成し、spanタグの後に追加
                $div = $dom->createElement('div');
                $div->setAttribute('id', 'search-results');
                $span->parentNode->insertBefore($div, $span->nextSibling);
            }
        }

        // 更新されたHTMLを取得
        $block_content = $dom->saveHTML($dom->documentElement);
    }

    // コンテンツを返す
    return $block_content;
}

// 関数をフィルターフックに追加
add_filter('render_block', 'add_htmx_attributes_and_elements_to_search_input', 10, 2);

// live_search関数を読み込み
include_once (plugin_dir_path(__FILE__) . 'ajax-functions.php');
.wp-block-search .wp-block-search__inside-wrapper {
  position: relative;
}
.wp-block-search .htmx-indicator,
.wp-block-search #search-results {
  position: absolute;
}
.wp-block-search .htmx-indicator {
  top: 2.5em;
}
.wp-block-search #search-results {
  top: 3em;
}

とかで、プラグイン作ってあげたら動きました。ハラショー!

A screenshot of a WordPress sample page showing a search input box with a search term entered in Japanese (あ). Below the search input, two search results are displayed, with one result titled "Htmx用の非公式のAPIがあったよ" and the other titled "Hello world!". The search input has a "Search" button to its right.

感想

動いたものの、外部からWP_Queryを500ms毎に走らせられるのはどうなん?という気持ちに。
既存のajax-functions.phpの内容を

function fetch_posts_with_cache() {
    // キャッシュキーを設定
    $cache_key = 'my_query_cache_key';
    // キャッシュの有効期限を設定(秒単位)
    $cache_duration = 30; // 例えば30秒

    // キャッシュからデータを取得
    $cached_posts = get_transient($cache_key);

    if (false === $cached_posts) {
        // キャッシュがない場合、WP_Queryを実行
        $args = array(
            'post_type' => 'post',
            'posts_per_page' => 10,
        );

        $query = new WP_Query($args);

        // クエリ結果をキャッシュに保存
        $cached_posts = $query->posts;
        set_transient($cache_key, $cached_posts, $cache_duration);
    }

    return $cached_posts;
}
?>

とかなんかでキャッシュ利用したりしたらマシなのかな?と思ったりしましたけれど、自己責任でどうぞ(責任持てないのでプラグイン公開しません…)

非公式なAPIありました

https://github.com/TCattd/HTMX-API-WP
さすがWordPress、非公式のHTMX用のAPIがありました。
どうしてもHTMX使ってやりたい!みたいな気持ちになっている人は試してみても良いかも。
だけれど、さらにメンテナー2人しかいないライブラリに乗っかるよりは素直に素のJSかReactか書いたほうが良くないか、という気はします。

Block周りで書くなら Interactivity API かも

WordPressは最近、Interactivity API( https://developer.wordpress.org/block-editor/reference-guides/packages/packages-interactivity/ )を出した(WordPressは何度も外部ライブラリ利用をしては開発止まっちゃうみたいなのを経験しまくっているので最近は最初から自作しちゃう派になりつつある気がする)ので、今から書くならそっち寄りにしたほうが良いかな、という気持ちに。

とはいえSTEP4みたいな強引なことするのだったら何だって良いか、という気もします。

Interactivity API は Alpine.js ライクに使える(ように完全にインスパイアされて開発されてる)ので、最近のイケてるJS開発に慣れているなら使えるってこの公式ブログ記事にも書かれていました。
https://developer.wordpress.org/news/2024/04/11/a-first-look-at-the-interactivity-api/

今のところの結論、WordPressでの使い処は

既存のクラシックテーマのサイトにちょいとAjaxな処理乗っけたい、みたいな時には良いと思います。
既存サイトのメンテナンスとか追加開発に導入を検討するのは全然アリだと思います。

https://wpmayor.com/htmx-might-be-a-big-deal-for-wordpress/

にもその良さが書かれていて頷ける気はします。
いいとこ取りしていきたい!

WordPressのことばっかりになってhtmxのこと殆ど学べてない心地…

その他、参照URL

set_transient() – Function | Developer.WordPress.org https://developer.wordpress.org/reference/functions/set_transient/

https://www.php.net/manual/ja/class.domdocument.php

とりあえずWordPressは置いといて、htmxの使い方知りたい場合
https://htmx.org/examples/
を上から順に試してみるのが良い気がします。
使いやすいシチュエーションもいっぱいあると思うので、試しておいて損はなさそう。

Discussion