mcpField
D
datastar

datastar

github.com/maragudk開発49110 回インストール31 週間前に更新
リポジトリ

datastar について

Datastar は、HTML 属性を使ってリアクティブな UI を構築する軽量フロントエンドフレームワークです。バックエンドの状態更新とフロントエンドの操作を組み合わせます。このスキルでは、Go アプリで gomponents から Datastar 属性を生成する gomponents-datastar も扱います。

主な機能

  1. 01HTML 属性によるリアクティブ状態管理
  2. 02テキスト・フォーム・動的スタイルのバインド
  3. 03イベントと修飾子による操作処理
  4. 04SSE による DOM とシグナルの更新
  5. 05gomponents 用 Go 属性関数

活用シーン

  1. 01サーバー描画ページへの操作性追加
  2. 02バックエンド状態に連動する UI の構築
  3. 03SSE によるリアルタイム画面更新
  4. 04Go で gomponents を使った Datastar 属性の記述

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/maragudk/skills --skill datastar

インストール後、Claude Code で/datastarを入力して読み込まれたか確認します。

詳細

Datastar

概要

Datastar は、ハイパーメディアを優先するアプローチにより、バックエンド主導のインタラクティブな UI を実現する軽量なフロントエンドフレームワークです。標準の HTML data-* 属性を使用して、バックエンドのリアクティビティ(htmx に類似)とフロントエンドのリアクティビティ(Alpine.js のようなもの)を組み合わせます。

このスキルを使用する場面

以下の場合にこのスキルを使用してください。

  • サーバーでレンダリングされた HTML にフロントエンドのインタラクティビティを追加する
  • バックエンドの状態によって駆動されるリアクティブな UI を構築する
  • Go アプリケーションで gomponents と Datastar を使用する
  • リアルタイム更新に Server-Sent Events(SSE)を使用する

前提条件: Go で Datastar を使用する場合は、HTML コンポーネントのパターンについて gomponents スキルも使用してください。

インストール

ブラウザー(CDN)
<script type="module" src="https://cdn.jsdelivr.net/gh/starfederation/[email protected]/bundles/datastar.js"></script>
Go(gomponents-datastar)
go get maragu.dev/gomponents-datastar

パート 1:Datastar の基礎

コアコンセプト

シグナル

シグナルはリアクティブな状態のコンテナーです。シグナルの値が変わると、依存するすべての式が自動的に更新されます。

<div data-signals="{count: 0}">
    <span data-text="$count"></span>
    <button data-on:click="$count++">Increment</button>
</div>
  • 式ではシグナル名の先頭に $ を付けます
  • シグナルに null または undefined を設定すると、シグナルが削除されます
  • ネストされたシグナルにはドット記法を使用します:$user.name
DOM のパッチ適用

Datastar はモーフィングを使用して、状態を維持しながら変更された DOM の部分のみを更新します。バックエンドは HTML フラグメントを送信し、既存のページにパッチを適用します。

属性リファレンス

状態管理

data-signals - リアクティブなシグナルを初期化します:

<div data-signals="{name: 'World', count: 0}"></div>

data-computed - 派生した読み取り専用のシグナルを作成します:

<div data-computed="{doubled: $count * 2}"></div>

data-init - 要素の読み込み時に式を実行します:

<div data-init="console.log('Loaded')"></div>
データバインディング

data-text - テキストコンテンツを式にバインドします:

<span data-text="'Hello, ' + $name"></span>

data-bind - フォーム要素の双方向バインディングです:

<input data-bind="$name" type="text">

data-show - 要素の表示/非表示を条件に応じて切り替えます:

<div data-show="$isVisible">Only shown when true</div>
スタイリング

data-class - CSS クラスを条件に応じて適用します:

<div data-class="{'active': $isActive, 'error': $hasError}"></div>

data-style - インラインスタイルを動的に設定します:

<div data-style="{'color': $textColor, 'opacity': $opacity}"></div>

data-attr - HTML 属性を動的に設定します:

<button data-attr="{'disabled': $isLoading}">Submit</button>
イベント

data-on - イベントリスナーを登録します:

<button data-on:click="$count++">Click me</button>
<input data-on:input="$search = evt.target.value">
<form data-on:submit__prevent="@post('/submit')">

evt 変数はイベントオブジェクトを参照します。

data-on-intersect - 要素がビューポートに入ったときに実行します:

<div data-on-intersect="@get('/load-more')">Loading...</div>

data-on-interval - 一定間隔で実行します:

<div data-on-interval="$elapsed++">Timer: <span data-text="$elapsed"></span></div>

data-on-signal-patch - シグナルが更新されたときに実行します:

<div data-on-signal-patch="console.log('Signals changed:', patch)"></div>
DOM の制御

data-ref - DOM 要素を参照するシグナルを作成します:

<input data-ref="$inputEl" type="text">

data-ignore - Datastar による処理から要素を除外します:

<div data-ignore>Third-party widget here</div>

data-ignore-morph - Datastar を有効なままにしつつ、モーフィングをスキップします:

<div data-ignore-morph>Preserve this DOM structure</div>

data-preserve-attr - モーフィング中に属性を保持します:

<input data-preserve-attr="value" type="text">
バックエンドアクション

@get(), @post(), @put(), @patch(), @delete() - バックエンドにリクエストを送信します:

<button data-on:click="@get('/api/data')">Load</button>
<button data-on:click="@post('/api/submit')">Submit</button>

修飾子

修飾子は、ダブルアンダースコア構文を使用して属性の動作を拡張します。

タイミング修飾子
  • __debounce / __debounce_500ms - 実行をデバウンスします
  • __throttle / __throttle_1s - 実行をスロットルします
  • __delay / __delay_200ms - 実行を遅延させます
イベント修飾子
  • __prevent - preventDefault() を呼び出します
  • __stop - stopPropagation() を呼び出します
  • __capture - キャプチャーフェーズを使用します
  • __passive - パッシブリスナーとして指定します
  • __once - 一度だけ実行します
  • __self - ターゲットが要素自身の場合にのみ実行します
  • __outside - 要素の外側でイベントが発生したときに実行します
  • __window - window にリスナーを登録します
修飾子の使用例
<input data-on:input__debounce_300ms="@get('/search?q=' + $query)">
<button data-on:click__once="@post('/track-click')">Track</button>
<form data-on:submit__prevent="@post('/submit')">

Server-Sent Events(SSE)

Datastar はストリーミングレスポンスに SSE を使用します。バックエンドは text/event-stream コンテンツタイプでイベントを送信します。

SSE イベントの種類

datastar-patch-elements - HTML を DOM にパッチ適用します:

event: datastar-patch-elements
data: elements <div id="content">Updated content</div>

datastar-patch-signals - シグナルの値を更新します:

event: datastar-patch-signals
data: signals {count: 42}

datastar-remove-elements - セレクターで要素を削除します:

event: datastar-remove-elements
data: selector #old-element

パート 2:gomponents-datastar

概要

gomponents-datastar は、gomponents ノードとして Datastar 属性を生成する Go 関数を提供します。gomponents ライブラリとシームレスに連携します。

インポート規約

すっきりした DSL にするには、ドットインポートを使用します。

import (
    . "maragu.dev/gomponents"
    . "maragu.dev/gomponents/html"
    data "maragu.dev/gomponents-datastar"
)

注:datastar には data エイリアスを推奨します。

最新の API ドキュメントを確認するには、次を実行します。

go doc maragu.dev/gomponents-datastar

関数リファレンス

状態管理

Signals - リアクティブなシグナルを初期化します。

data.Signals(map[string]any{
    "count": 0,
    "name":  "World",
})

Computed - 計算済みシグナル(キーと値のペア)を作成します。

data.Computed("doubled", "$count * 2")

Init - 読み込み時に式を実行します。

data.Init("console.log('Component loaded')")
データバインディング

Text - テキストコンテンツをバインドします。

Span(data.Text("'Hello, ' + $name"))

Bind - フォームを双方向バインドします。

Input(Type("text"), data.Bind("$name"))

Show - 表示を条件付きで切り替えます。

Div(data.Show("$isVisible"), Text("Shown when visible"))
スタイリング

Class - クラスを条件付きで適用します(キーと値のペア)。

data.Class("active", "$isActive", "error", "$hasError")

Style - インラインスタイルを動的に設定します(キーと値のペア)。

data.Style("color", "$textColor", "opacity", "$opacity")

Attr - 属性を動的に設定します(キーと値のペア)。

data.Attr("disabled", "$isLoading", "aria-busy", "$isLoading")
イベント

On - イベントリスナーを追加します。

data.On("click", "$count++")
data.On("click", "@post('/submit')", data.ModifierPrevent)
data.On("input", "$search = evt.target.value", data.ModifierDebounce)

OnIntersect - ビューポートとの交差を検知します。

data.OnIntersect("@get('/load-more')")

OnInterval - 定期的に実行します。

data.OnInterval("$elapsed++")

OnSignalPatch - シグナルの変更に反応します。

data.OnSignalPatch("console.log('Updated')")
DOM 制御

Ref - DOM 要素への参照を取得します。

data.Ref("$inputEl")

Ignore - Datastar による処理をスキップします。

data.Ignore()

IgnoreMorph - モーフィングのみをスキップします。

data.IgnoreMorph()

PreserveAttr - モーフィング中も属性を保持します。

data.PreserveAttr("value", "checked")
リクエストヘルパー

Indicator - 読み込み状態を表示します。

data.Indicator("$isLoading")

JSONSignals - 送信するシグナルを制御します。

data.JSONSignals(data.Filter{Include: "form.*"})
data.JSONSignals(data.Filter{Exclude: "internal.*"})

モディファイア

イベント関数とモディファイア定数を組み合わせて使用します。

// Timing
data.ModifierDebounce  // __debounce
data.ModifierThrottle  // __throttle
data.ModifierDelay     // __delay

// Event behavior
data.ModifierPrevent   // __prevent
data.ModifierStop      // __stop
data.ModifierCapture   // __capture
data.ModifierPassive   // __passive
data.ModifierOnce      // __once
data.ModifierSelf      // __self
data.ModifierOutside   // __outside
data.ModifierWindow    // __window

// Duration/threshold helpers
data.Duration(500 * time.Millisecond)  // __500ms
data.Threshold(0.5)                     // __threshold_0.5

完全な例

package views

import (
    "net/http"

    . "maragu.dev/gomponents"
    . "maragu.dev/gomponents/html"
    ghttp "maragu.dev/gomponents/http"
    ds "maragu.dev/gomponents-datastar"
)

func CounterPage() Node {
    return HTML5(HTML5Props{
        Title:    "Counter",
        Language: "en",
        Head: []Node{
            Script(
                Type("module"),
                Src("https://cdn.jsdelivr.net/gh/starfederation/[email protected]/bundles/datastar.js"),
            ),
        },
        Body: []Node{
            Div(
                data.Signals(map[string]any{"count": 0}),

                H1(data.Text("'Count: ' + $count")),

                Button(
                    data.On("click", "$count++"),
                    Text("Increment"),
                ),

                Button(
                    data.On("click", "$count--"),
                    Text("Decrement"),
                ),

                Button(
                    data.On("click", "@post('/api/save')"),
                    Text("Save to Server"),
                ),
            ),
        },
    })
}

func SearchForm() Node {
    return Form(
        data.Signals(map[string]any{"query": "", "results": []any{}}),
        data.On("submit", "@get('/search?q=' + $query)", data.ModifierPrevent),

        Input(
            Type("text"),
            data.Bind("$query"),
            data.On("input", "@get('/search?q=' + $query)", data.ModifierDebounce, data.Duration(300*time.Millisecond)),
            Placeholder("Search..."),
        ),

        Div(
            ID("results"),
            data.Show("$results.length > 0"),
            data.Text("'Found ' + $results.length + ' results'"),
        ),
    )
}

SSE ハンドラーパターン

func handleSSE(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache")
    w.Header().Set("Connection", "keep-alive")

    flusher, ok := w.(http.Flusher)
    if !ok {
        http.Error(w, "SSE not supported", http.StatusInternalServerError)
        return
    }

    // Patch HTML elements
    fmt.Fprintf(w, "event: datastar-patch-elements\n")
    fmt.Fprintf(w, "data: elements <div id=\"content\">Updated!</div>\n\n")
    flusher.Flush()

    // Update signals
    fmt.Fprintf(w, "event: datastar-patch-signals\n")
    fmt.Fprintf(w, "data: signals {\"count\": 42}\n\n")
    flusher.Flush()
}

ヒント

  1. シグナルの命名: Go コード内ではなく、式の中で $ 接頭辞を使用します
  2. 競合の回避: Datastar のテキストバインディングには data.Text() を、静的コンテンツには gomponents の Text() を使用します
  3. モディファイア: 複数のモディファイアを連結できます:data.On("click", "...", data.ModifierPrevent, data.ModifierOnce)
  4. SSE ID: SSE 経由でパッチを適用する要素には、一致する id 属性が必要です
  5. モーフィング: Datastar はデフォルトで、DOM の更新中もフォーム入力の状態を保持します

コメント