軽量なJavaScriptライブラリ Alpine.jsを使ってみよう

こんにちは。
今回は軽量なJavaScriptライブラリであるAlpine.jsについて紹介したいと思います。

以前、このブログの中でhtmxに関する記事を書きました。私はハイパーメディアシステムという書籍でhtmxの存在を知りましたが、Alpine.jsもhtmxと同様にこの書籍の中で紹介されており、そこで初めて存在を知りました。

簡単に説明すると、JavaScriptのオブジェクトとDOMの双方向バインディングを手軽に実現する軽量なJavaScriptライブラリです。htmx同様、元々SPAで構築されているWebアプリケーションにおいてはAlpine.jsを導入するメリットが得られるケースは多くないと思われます。一方で、MPA(Multi-Page Application。SPAではない、リクエストの度に画面遷移をする従来型のWebアプリケーション)構成のWebアプリケーションのフロントエンドにおいて、Vue.jsのような双方向バインディングをサクッと実現したい場合にはメリットが多く得られる便利なライブラリです。
また、Alpine.jsでは<script>要素の中にJavaScriptのコードを書かず、HTML要素の属性にJavaScriptのコードを書くという点も特徴的です。

Alpine.jsの公式サイトは以下になります。

Alpine.js

A rugged, minimal framework for composing behavior directly in your markup.

https://alpinejs.dev

Alpine.jsは、Vue.jsを少しでも触ったことのある人にとってはかなりなじみやすい構文になっています。Vue.jsは、HTMLタグの中にディレクティブと呼ばれるv-からはじまる属性(例えばv-if,v-for, v-modelなど)を付けることで、DOMを動的に操作します。
一方のAlpine.jsでは、x-から始まる属性を付けることでDOMを操作します。x-dataなど、Vue.jsにはない属性もありますが、x-if, x-for, x-modelなど、Vue.jsとほとんど同じような使い方ができる属性が多くあります。

以下は、公式サイト内にあるカウンターのサンプルです。

<div x-data="{ count: 0 }">
	<button x-on:click="count++">Increment</button>
	<span x-text="count"></span>
</div>

ブラウザにはボタンが1つ表示され、クリックする度にカウントアップされる数値がボタンの横に表示されます。

Alpine.jsでは変数定義や関数の定義をx-data属性の中で行うという点です。Vue.jsでは、変数や関数はscriptタグの中で定義します。一方でAlpine.jsはscriptタグを使用することなく、JavaScriptのコードをHTMLタグの中で完結させることができます。

コードは分けるべきかまとめるべきか

HTML要素の中にJavaScriptのコードを書くことは、一見するとコードが読みにくくなると感じ、このような書き方を好まない人もいるかもしれません。確かに、関心の分離という観点からは、HTMLとJavaScriptのコードは分けて書く方が良いとされています。しかし、HTMLとJavaScriptの場合、それぞれが密接に関連しているため、コードを分離させてもメリットが少ない場合もあります。

サンプルとして、ボタンをクリックした際に画面にメッセージを表示するようなWebページを考えます。Webページ上のボタンをクリックした際にJavaScriptの処理を実行する方法は、大きく2つの方法があります。ここでは、ライブラリを使用しない、素のJavaScript(バニラJS)で実装する場合を考えます。

方法1. onclickを使う

まず1つ目の方法は、onclick属性を使って関数を呼び出す方法です。以下は、ボタンクリック時に、div要素に「Hello」と出力するコードです。

function hello() {
  document.getElementById('result').textContent = 'Hello!';
}
<button type="button" onclick="hello()">click me</button>
<div id="result"></div>

あらかじめJavaScriptで関数を定義しておき、onclick属性でその関数を呼び出します。

方法2. イベントリスナーを使用する

2つ目の方法は、イベントリスナーを使用する方法です。
以下はイベントリスナーを使用する場合のサンプルコードです。

<button type="button" id="button">click me</button>
<div id="result"></div>
document.getElementById('button').addEventListener('click', () => {
	document.getElementById('result').textContent = 'Hello!';
});

button要素にはid属性を追加しました。JavaScriptでは、idを元に要素を取得し、クリック時のイベントを定義します。

推奨される方法

では、onclickを使用する方法と、イベントリスナーを使用する方法ではどちらの方が推奨されるのでしょうか?

状況にもよりますが、一般的にはイベントリスナーを使用する2の方法が推奨される場面が多いです。理由は、その方がHTMLとJavaScriptのコードを分離できるため、可読性が上がり、拡張がしやすくなると考えられているためです。

onclickを使用する方法では、ボタン要素にJavaScriptの関数名が含まれています。一方で、イベントリスナーを使用する方法では、ボタン要素にはid属性が追加されているだけで、JavaScriptのコードは含まれません。そのため、HTMLではマークアップに専念することができ、それぞれで独自に機能を追加したりしやすくなりという理屈です。

依存がある以上、別ファイルでも一緒に直す

HTML、JavaScript、CSSは役割が違うため、それぞれコードを分けた方が良い主張はもっともです。しかし、実際にはコードを分離しても修正が片方だけで完結するケースは意外と少ないです。

例えば、以下のようにHTMLにてボタン要素のid属性の値を変更してしまった場合を考えます。

<!-- id:button → btn に変更 -->
<button type="button" id="btn">click me</button>

JavaScriptではid属性の値によって要素を取得していたため、idの値を変更した場合、JavaScriptのコードも修正する必要があります。

また、結果の出力先をdiv要素からinput要素に変更した場合はどうでしょうか。その場合は、結果をtextContentにセットするのではなく、valueにセットするように変更する必要があります。

つまり、HTMLとJavaScriptのコードを分離したとしても、それぞれのコードはお互い密接に関係し合っており、修正や機能追加が必要になった場合、両方修正することがほとんどです。これはJavaScriptだけに限らず、CSSでも同じようなことが言えます。HTML・JavaScript・CSSのコードを分離することは、それぞれのコードを単体では読みやすくするメリットはあるものの、変更容易性や拡張性はそれほど高くはならないケースが多いです。

最近ではVue.jsなどのSPAフレームワークでは、コンポーネントという単位で関心を分離し、コンポーネントの中でマークアップ(HTML)とスタイル(CSS)と処理(JavaScript)をまとめて管理する方式が比較的一般的になりました。

ここで、改めてAlpine.jsのコードを眺めてみます。
先ほどのコードよりも少長めのコードで、こちらも公式サイトにあるコードです。
これは、input要素の下にリストがあり、input要素に入力された値で動的に検索を行うHTMLです。

<div
	x-data="{
		search: '',

		items: ['foo', 'bar', 'baz'],

		get filteredItems() {
			return this.items.filter(
				i => i.startsWith(this.search)
			)
		}
	}"
>

	<input x-model="search" placeholder="Search...">

	<ul>
		<template x-for="item in filteredItems" :key="item">
			<li x-text="item"></li>
		</template>
	</ul>

</div>

HTMLのコードとJavaScriptのコードが混在しているため、慣れていないと読みづらいと感じる人もいるかもしれません。しかし、このコードの影響範囲はこのdiv要素のみでおさまるため、修正や機能拡張が発生した際の修正箇所を明確にしやすくなります。可読性はもちろん重要ですが、長期的にメンテナンスされるコードにおいては、「変更時にどこを直せばいいか」が局所化されて明確になっている方がメリットが大きいです。
そういう観点で、Alpine.jsは修正箇所を明確化しやすいライブラリと言えます。

使いどころ

htmxの補助として

Alpine.jsはhtmxの書籍でも紹介されていたこともあり、相性は良いです。
htmxを使用すると、任意のHTML要素で、任意のタイミングでHTTPリクエストを発行できるようになり、レスポンスのHTMLを任意の要素に差し替えることができます。それにより、SPAでしか実現が難しかったようなユーザー体験を実現できるようになります。

一方で、htmxはサーバーとの通信を前提とした動作にしか適用できません。つまり、サーバーとの通信を必要とせず、クライアント側だけで実現したい高度なUI制御には向いていません。

例えば、以下のようなUIです。

  • 動的な表示非表示の切り替え
  • 動的な入力可否の切り替え
  • 動的な入力項目の追加
  • 一覧画面のチェックボックスの一括チェック

このようなUI操作は基本的にサーバーとの通信が不要なため、htmxでは対応できません。
JavaScriptを書くことで実現可能ですが、動的な入力制御などはライブラリを使用しないとコードが手続き的になって読みづらく、コード量も多くなりがちです。

Alpine.jsを使えば、必要なスコープで変数を定義し、双方向バインディングを実現することで上記のような処理が簡単に実現可能です。

jQueryの変わりとして

Alpine.jsは、公式サイトでモダンなjQueryのようなものと説明されています。

jQueryは、バニラJSでそのまま書くと長くなりがちなコードを短く書きたいと思ったときにはすごく有効で、今でも使える場面はそれなりにあるだろうと思います。しかし、jQueryではどうしてもコードを手続き的に書く必要があるため、高度なDOM操作をしようと思うとどうしてもコード量は増えてしまいます。

高度なDOM操作をしたい場合は、変数とDOMが双方向バインディングされる仕組みを使う方が圧倒的に簡単であり、そのような仕組みサクッと実現できるのがAlpine.jsです。

サンプルコードと簡単な解説

せっかくなので簡単なサンプルコードを見ながら、Alpine.jsに少しだけ触れます。こんなことができる、をざっくりでも感じてもらえると嬉しいです。

<table x-data="{
selected: [],
people: [
	{ id: 1, name: '山田太郎', age: 28, job: 'エンジニア' },
	{ id: 2, name: '鈴木花子', age: 34, job: 'デザイナー' },
	{ id: 3, name: '佐藤次郎', age: 45, job: 'マネージャー' }
  ],
  selectAll() {
	if (this.selected.length === this.people.length) {
	  this.selected = [];
	} else {
	  this.selected = this.people.map(person => person.id);
	}
  }
}">
	<thead>
	  <tr>
		<th><input type="checkbox" @click="selectAll()" :checked="selected.length === people.length"></th>
		<th>名前</th>
		<th>年齢</th>
		<th>職業</th>
		<th>編集</th>
	  </tr>
	</thead>
	<tbody>
	  <template x-for="person in people" :key="person.id">
		<tr>
		  <td><input type="checkbox" :value="person.id" x-model.number="selected"></td>
		  <td x-text="person.name"></td>
		  <td x-text="person.age"></td>
		  <td x-text="person.job"></td>
		  <td>
			<template x-if="selected.includes(person.id)">
			  <button type="button">編集</button>
			</template>
		  </td>
		</tr>
	  </template>
	</tbody>
</table>

このコードの実際のブラウザで表示した結果は以下のようになります。

挙動としては以下の動作になります。

  • ヘッダーのチェックボックスをチェックすると、全ての行のチェックボックスが連動してチェック、及び解除される
  • チェックがついている行のみ編集ボタンが表示される

簡単な解説

以下は各属性についての簡単な解説です。

  • Alpine.jsでは、x-data属性を指定した要素配下で、x-*の属性が使用できるようになる
  • x-dataでは変数や関数(オブジェクトのプロパティとメソッド)が定義できる
  • x-dataで定義したプロパティやメソッドはその要素配下のみのスコープとなる
  • @clickx-on:clickと同義で、クリック時のイベントを定義できる
  • x-forx-ifはtemplateタグで使用でき、繰り返しや条件による要素の出し分けを制御できる
  • x-modelで、input要素のvalueとx-dataのプロパティを紐づけることができる
  • x-ifx-for<template>タグで使用する

まとめ

個人的には使いやすいライブラリだと感じており、今後も積極的に使っていきたいと思っています。最近は関わる案件がMPAのWebアプリケーションであることが多いため、相性が良く、使える場面は多くありそうです。
私が経験している案件の1つで、Java(SpringBoot)で開発しているWebアプリケーションがあります。そのアプリケーションはSPAではありませんが、自動保存や複雑な入力制御が必要な画面があり、その画面では部分的にVue.jsを導入しています。その当時はhtmxやAlpine.jsを知らなかったため、Vue.jsが適切だと思って技術選定しましたが、今改めて同じ機能を実装するとしたら、フロントはhtmx + Alpine.jsの技術スタックを選択するように思います。htmxで自動保存し、Alpine.jsでフォームの状態を制御する。これでかなりコードがスッキリ書けるのではないかと思います。

SPAではないWebアプリケーションに携わっている方は、技術選定の候補としてAlpine.jsを検討してみてはいかがでしょうか。技術選定をする際の参考になれば幸いです。

参考

ハイパーメディアシステム | 技術評論社

近年急速に注目を集めるフロントエンドライブラリ「htmx」について、その作者自身らが執筆した解説書です。 htmxの魅力はそのシンプルさにあります。そのシンプルさは、30年にわたりウェブを支えてきたハイパーメディアの力を再発見し、HTMLそのものを拡張するという発想から生まれました。 本書の第1部では、ハイパーメディアの歴史としくみを紐とき、ウェブの根幹でありながら現代では誤解されがちなRESTの本来の概念を丁寧に解説します。そして、Web 1.0スタイルのアプリケーションを作成しながら、ハイパーメディアの中核的なコンセプトをおさらいします。 第2部では、ハイパーメディアの力を最大限に引き出すhtmxのしくみと使い方を紹介します。「あらゆる要素からHTTPリクエストを発行できる」「任意のイベントでリクエストをトリガできる」といったhtmxの基本概念を、実際にウェブアプリケーションを作成しながら学びます。 第3部では、モバイル版のhtmxともいえる「Hyperview」を紹介します。モバイルアプリにもハイパーメディアの概念を取り入れることで、htmxと同様の強力さとシンプルさを兼ね備えたモバイルアプリケーションを作成できることを示します。 複雑化した現代のフロントエンド開発に疲れてしまった方は、本書を手にぜひ一度、htmxにチャレンジし、ハイパーメディアシステムとしてのウェブの本来の力を再発見してください。こんなにシンプルで軽やかなウェブ開発の方法があったのかと、きっと驚かれるはずです。

https://gihyo.jp/book/2025/978-4-297-14945-1
Alpine.js

A rugged, minimal framework for composing behavior directly in your markup.

https://alpinejs.dev