Stimulus – 受託Web屋に「ちょうどいい」JavaScriptフレームワーク

ネコメシでは週に1回、持ち回り制で勉強会を開催しています。各々が気になっているトピックについてスライドを作って、30分~1時間くらいの発表を行います。

先日の勉強会にて、JavaScriptフレームワークのStimulusについて発表したので、その内容を公開します。

Stimulusは控えめなフレームワークで、非SPAのウェブサイト制作(コーポレートサイト、キャンペーンサイト等)において非常に強力なツールだと思います。jQueryをメインに使っている制作者や、オブジェクト指向的にコードを書こうとしているけどいまいちコレといった腹落ちができていない人には、特におすすめできるものです。

Stimulus

受託Web屋に「ちょうどいい」JavaScriptフレームワーク

今日はStimulusというJSフレームワークを紹介します。

多分に実装の話題なので、デザイナーさんたちにはちょっとわからない話かもしれないですがご容赦ください。ただ、最初の方の話はデザイナーでもわかる話だと思うので、ちょっとだけ聞いてみてください。

さっそくですが、謎の質問をしたいと思います。

問:控えめなJavaScriptは好きですか?

  • 好き
  • 嫌い
  • どちらでもない
  • よくわからない

控えめなJavaScriptは好きですか? というか、控えめなJavaScriptと言われてピンときますか?

春日井さん(注:弊社長)は世代的にピンと来ていると思いますが、他の人はどうでしょう? ちなみに私は好きです。

では、控えめなJavaScriptとは何か、かんたんに説明します。

控えめなJavaScriptとは?

WebページにとってのJavaScriptがどうあるべきかの一つの答え。

特徴

  • まずマークアップありき
  • ユーザビリティやアクセシビリティを損なわない
  • JavaScriptが無効でもちゃんと使える

WebページにとってのJavaScriptがどうあるべきかの一つの答え。だと私は思います。

特徴は、まずマークアップありきです。静的なHTMLのことです。そしてHTMLが持っている機能、ユーザビリティ、アクセシビリティを損なわないように、あるいはそれらを増強させたり、使いやすさを上乗せしたりする形で、JavaScriptを実装する、という考え方です。

似たような概念としてプログレッシブ・エンハンスメントとか、グレースフル・デグラデーションと呼ばれる考え方があって、そっちの方が用語としてメジャーかもしれません。それらに通底するのは、より多様な人に対してアクセシブルで、堅牢で可用性の高いものを提供しようという考え方です。

控えめなJavaScriptの例

Lightbox

オリジナル画像をモーダルで拡大して表示する
https://lokeshdhakar.com/projects/lightbox2/

具体例を見たほうがわかりやすいと思うのでいくつか紹介します。

Lightboxはモーダルで画像や動画をギャラリーっぽく表示する先駆けになったライブラリです。すごく昔からあります。

このスライドを作るにあたって、LightboxのREADMEを読んだんですが、「8年たった今もちゃんと動いてるぜ!」と力強いメッセージが書かれていました。しかも、よくみたら、そのテキストが書かれたのが5年前でした。

Lightboxは控えめに作られていて、それぞれの画像はオリジナル画像への単純なリンクでマークアップされています。Lightboxがこのリンク先を読み取って、リンククリックを乗っ取る形でUIを展開しています。

JSをオフにすると、単純なページ遷移にフォールバックされることがわかると思います。

控えめなJavaScriptの例

Select2

セレクトボックスに検索機能や複数選択機能などを追加する
https://select2.org/

Select2はセレクトボックスを拡張するライブラリです。選択肢を検索で絞り込んだり、タグを選ぶ的な複数選択のUIを提供してくれます。

セレクトボックスをかっこよくするだけなら、見た目だけかっこいいものを被せて、クリックして展開するのはネイティブのものを使うことが多いんですが、Select2は展開後のUIも独自に実装しています。そのぶん、アクセシビリティが犠牲にならないように慎重に実装されています。キーボード操作はできますし、ARIAの属性もちゃんと使われていて、けっこうデラックスな実装です。

控えめなJavaScriptというときの「控えめ」は、機能の控えめさではなくて、「JSが有効なことを絶対視しない」という意味での控えめさと捉えるといいと思います。だから実装が分厚いことは必ずしも悪いことではない。JSが使えないときに、使い勝手はわるくとも機能へのアクセシビリティを担保するということが、控えめたる必要条件です。

控えめなJavaScriptの例

あれこれポップアップ

リンクや abbr タグから属性値を拾ってツールチップ拡張を表示する
半透明ポップアップが出てる様を眺めるにつけ、かっこよすぎ!って自画自賛したいアルヨー。

http://www.remus.dti.ne.jp/a-satomi/bunsyorou/ArekorePopup/sample.html

あれこれポップアップはリンクや abbr タグから、title属性やhref属性の値を拾って、ツールチップにかっこよく表示してくれるライブラリです。このライブラリは僕に「控えめとはどういうことか」を教えてくれた思い出深いライブラリで、僕にとってはとてもエポックメイキングな出来事でした。

このライブラリの作者の方は、のちにネコメシという会社を立ち上げています。(わらうところ 😑)

Stimulus の概要

ウェブサイトから引用

Stimulus is a JavaScript framework with modest ambitions. Unlike other frameworks, Stimulus doesn’t take over your application’s entire front-end. Rather, it’s designed to augment your HTML by connecting elements to JavaScript objects automatically.
Stimulusは控えめな野心をもったJavaScriptフレームワークです。ほかのフレームワークとちがって、フロントエンド全体を占領することはありません。要素がJavaScriptのオブジェクトに自動的に接続されるように、ちょっと手助けするだけです。

というわけでStimulusの紹介です。まず公式サイトに載っているテキストを引用します。

Stimulusは控えめな野心をもったJavaScriptフレームワークです。ほかのフレームワークとちがって、フロントエンド全体を占領することはありません。要素がJavaScriptのオブジェクトに自動的に接続されるように、ちょっと手助けするだけです。

と。控えめであるということと、あんまり多くのことをしてくれなさそうなことがわかると思います。

Stimulus の概要

特徴

  • 控えめなJavaScript
  • 既存のHTMLに機能をつける
    • 機能 = 補完・UX向上・演出
    • あくまで脇役につとめる
  • 状態をDOMで持つ
  • DOMの変化を監視する
  • 10 KB (gzipped) の小さなライブラリ
  • Basecamp 製 (ex. 37signals)

今時のフロントエンドフレームワークといえば、AngularとかReactとか、ひとたびそこに足を踏み入れたら、ウェブサイト全体をそのフレームワークありきの作り方をせざるをえなくなるものが多いです。いっぽうでStimulusは非常に控えめで、既存のライブラリとほぼ干渉しないように作られています。

Stimulusは状態をDOMに持つという、いまどき珍しいつくりをしています。状態をDOMに持つということは、静的サイトとか、PHPやRailsでサーバーサイドでレンダリングされる仕組みと大変相性がよいです。Backbone.jsが出てからは、状態をモデル、つまりJavaScriptのオブジェクトとして保持して、それをテンプレートにはめ込んで描画するパラダイムが主流になってきているのですが、Stimulusはそれとは逆の作りをしています。

Basecampというアメリカの会社で開発されています。Ruby on Railsを作ったDHHが在籍している会社で、DHHはStimulusの起源についてサイトに気持ちのこもった文章を書いています。この文章はシフトブレインのエンジニアの安田くんが翻訳しているので、読んでみるといいと思います。

SPAじゃないプロジェクトのための控えめなJavaScriptフレームワーク「Stimulus」

この機能、どう書く?

ユーザーの入力に応じてテキストを表示するスクリプト

  • ユーザーはテキストボックスに任意の文字を入力する
  • Greet ボタンを押すとテキストが表示される
  • テキスト内容は「Hello, 名前!」となるようにする

Stimulusをどう書くか、を説明するために一つの例を挙げたいと思います。

  • ユーザーはテキストボックスに任意の文字を入力する
  • Greet ボタンを押すとテキストが表示される
  • テキスト内容は「Hello, 名前!」となるようにする

これをjQueryをつかって実装するとして、今まではどうしていたかというと、

これまでの書き方だと…

<div class="Hello">
  <input class="Hello__name" type="text">
  <button class="Hello__greetButton">Greet</button>
  <span  class="Hello__output"></span>
</div>
import $ from "jquery"

class Hello {
  constructor(el) {
    this.$el = $(el)
    this.$name = this.$el.find('.Hello__name')
    this.$greetButton = this.$el.find('.Hello__greetButton')
    this.$output = this.$el.find('.Hello__output')
    this.bindEvents()
  }
  bindEvents() {
    this.$greetButton.on('click', () => this.greet())
  }
  greet() {
    const name = this.$name.val()
    this.$output.text(`Hello, ${name}!`)
  }
}

$('.Hello').each((i, el) => {
  new Hello(el)
})

※ネコメシっぽい書き方

こんな感じになります。

Helloというクラス名の付いたマークアップをして、テキストボックス、Greetボタン、出力を表示するための箱を用意しておきます。

JSはこのくらいの分量になります。やっていることは、まずクラスの定義です。Helloクラスとして、コンストラクタ関数のなかで、各要素の収集と、イベントリスナーの登録をします。そしてgreet関数で、テキストを出力します。

クラスの定義をしたら、初期化もしなきゃいけません。僕はこのように、BEMクラス名で要素をeachで回して、インスタンスを作ることが多いです。

jQueryを使うとこのような実装になります。

Stimulus の書き方

HTML

<div data-controller="hello">
  <input data-target="hello.name" type="text">
  <button data-action="click->hello#greet">Greet</button>
  <span data-target="hello.output"></span>
</div>
  • data-controller 属性でHTMLにコントローラー(処理系)を割り当てる
  • data-action 属性でイベントハンドリング
  • data-target 属性でDOMへの参照を得る

次にStimulusでの書き方です。まずHTMLは、data属性を使ってアノテーションを付けます。

  • data-controller 属性でHTMLにコントローラーを割り当てています。ここではhelloコントローラーを要素と紐づけています。これによって new Hello のようにクラスのインスタンス化がされるイメージです。

  • data-action 属性でイベントハンドリングをします。ボタンがクリックされたら、helloコントローラーのgreetメソッドを呼びますよ、という指定です。

  • data-target 属性でDOMへの参照を得ることができます。JSの中から要素を簡単に呼び出せるようにしています。これによって、JSで必要になる要素をいちいち探してくる必要がなくなります。

Stimulus の書き方

JavaScript

// hello_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [ "name", "output" ]

  greet() {
    this.outputTarget.textContent =
      `Hello, ${this.nameTarget.value}!`
  }
}
  • Controller を継承するクラスを定義
  • {name}_controller.js の名前で保存
  • data-target で設定した名前を targets 静的メンバーに列挙する

JSはクラス構文を使って書きます。Controllerを継承するかたちで書きます。ファイル命名規則が決まっていて、ファイル名がコントローラーの名前として自動的に登録されるようになっています。

クラスの静的メンバー変数として targets を設定します。HTMLの data-target 属性で設定した要素を引っ張ってくるために必要です。このように設定すると、thisコンテキスト上で、名前に「Target」を付けた変数名でDOMが参照できるようになります。

Stimulusはこれだけで動きます。

書き方におけるメリット

  • コントローラーのインスタンス化がらくちん
    • 要素を自動的に探してくれる
  • イベントの登録がらくちん
    • bind, unbind を気に掛ける必要なし
  • 要素の取得がらくちん
    • クラス名をJSに持たずに済む

書き方におけるメリットをまとめるとこんな感じです。

まず、コントローラーのインスタンス化がらくちんです。jQueryでやっていたように、要素を探してeachで回してインスタンス化して回る必要がありません。

イベントリスナーの登録もJSからは不要でした。

要素の取得もらくちんでした。JSからクラス名指定で要素を探す必要はありません。

書き方におけるメリットは、こんなふうにらくちんになることです。

本質的なメリット

  • クラス名をJSに持たない=関心の分離を促進
    • コントローラーの再利用性アップ
  • HTMLの属性とJavaScriptの繋ぎしかしないこと
  • DOMの変化を監視する
    • 新しく追加された要素に対し自動的にコントローラーが割り当てられる

より本質的なメリットとしては、クラス名をJSに持たないため、コントローラーはHTMLのクラス名に関心をもたない。知らなくていいということになります。そのためコントローラーはより再利用性の高いものにしていくことが可能です。

次に、StimulusはHTMLの属性とJavaScriptの繋ぎしかしません。したがって、Stimulusの中で他のライブラリを使ったり、反対に、他のライブラリの中でStimulusを活かしたりといった使い方が柔軟にできます。

3つ目のDOMの変化を監視するというのは、概要説明のところではしょったのですが、実はこれが最も重要で一番大きいメリットだと嶌田は思います。

DOMを監視するとはどういうことか?

これで動くということ:

$(document).append(
  '<button data-controller="like-button" data-action="like-button#toggle">いいね!</button>'
)

これだけ!

どういうことかというと、ページが初期化されたあとに、動的にページ内に追加される要素を監視してくれるのです。追加される要素に data-controller 属性が付いていたら、自動的に要素をコントローラーと紐づけてくれます。これが非常に強力です。

DOMの変更検知にはMutationObserverというDOM標準の機能が使われていて、IE 11以降のモダンブラウザで動作します。

DOM変更検知がうれしいユースケース

  • Pjax
  • モーダルのなかにタブがある
  • コメント投稿

Pjax

通常のページ遷移に対して、ページの一部分を動的に書き換えてページを切り替えるのがPjax。リンク先のHTMLをAjaxで読み込み、一部分を取り出して現在のページに反映する。遷移先のページにはJSで動作するいろんなものがあるかもしれないが、Stimulusを使えば遷移後のコールバックを実行する必要なしに自動的に動いてくれる。

既存のPjaxライブラリと併用できるのもいいところ。特に設定も不要。

タブをモーダルで表示する

モーダルの中にJSで動くUIが置かれることがある。モーダルにHTMLをappendするかたちで発動するタイプのモーダルの場合、モーダルを開いた後にUIの初期化処理が必要。Stimulusを使うと初期化処理の手間が省ける。

コメント投稿

GitHubみたいなUI。APIを使ってコメントを投稿し、画面遷移なしで結果を画面に表示する。コメントにはLikeボタンや編集ボタンが置かれている。これらを動かすためにも、data属性のついた要素をDOMのappendするだけでおしまいです。初期化のコードを書く必要はありません。

Pjaxのデモ

動くデモをしました(あんまり受けがよくなかったので手直しして公開するかもしれない)。

受託Web制作案件との親和性

僕はStimulusと受託Web制作案件の親和性が高いのではないかと踏んでいます。とくにサーバーサイド組み込み用のHTMLテンプレートを実装する仕事には向いているでしょう。

jQueryをメインに使っていて、機能の初期化やイベントハンドリングのやり方、画面内に複数のインスタンスを持つときの管理の仕方に「これ」といったものを持っていないエンジニアは、ぜひ一回試してみるといいと思います。

SPAの仕事がきらきらと目立っている昨今だけれど、Stimulusが輝くタイプの案件もまだまだたくさんあるはずです。

さらに、フロントエンド実装とテンプレート組込み担当者とのコミュニケーションの質を改善させる糸口もあるのではないかと思っています。

サーバーサイドの組込み担当者の人にとって、よりわかりやすいHTMLを渡すことができれば、こちらの意図したHTMLを壊されることも減り、結果的に質の良いものが出来上がる。それはネコメシにとってもうれしいことです。

今後の取り組み

  • 案件で採用する。続けて使っていけるか検証する
  • よさそうだったら広めていきたい
  • 会社推奨のライブラリになるかも?

みなさんも使ってみて、感想を教えてください。

おわり。