28
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Stimulus に入門した(最小構成)

Last updated at Posted at 2021-09-24

Stimulus というのを試してみたかった。
Rails とか関係なく,静的サイトで,Node.js とかも一切使わず,最小限のファイルだけでどういうふうに使えるものなのか知りたかった。
意外と苦労したので記録しておく。

著者のプロフィールは以下のとおり:

  • React とか Vue とかまっったく知らん
  • そもそも JavaScript は旧石器時代の知識で止まっている
    • const と let はのちに勉強したので知ってる
    • クラスなんか知らん
      • 「JavaScript にはクラスなんか無い」は旧石器時代の話らしい
    • モジュールとか => とかは知らん
  • npm とか yarn とか使ったことはある
    • 意味も分からず参考にした記事のとおりコマンド打っただけ
  • Webpack も知らん
    • 名前は知ってる
    • 何をするものなのかも,ぼんやりとは知っている

この記事では,Stimulus とは何か,ということについては述べないし,Stimulus の諸概念(ターゲットだのアクションなど)についても述べない。焦点を「Stimulus を読み込んで単純なデモが動くようにする」ことに当てる。
また,基本的にド素人が書いているので,人に教えるための記事ではなく自分が転びながら門をくぐった単なる記録となっている。

なお,「試行錯誤の過程はどうでもいいから,その最小構成の Stimulus のサンプルとやらを早く見せろや」という方は,「方針」の節に目を通したあと「正しいコード」の節以降を読んでいただければと思う。

方針

最初にやってみるのは,やはり
https://stimulus.hotwired.dev/
のページに載っているものを手元で再現すること。

つまり,入力欄に「Ruby」と打って,「Greet」ボタンを押せば「Hello, Ruby!」と表示されるだけのデモ1
Stimulus 界(?)のハローワールドだね。

HTML ファイル(index.html とする)を一つ書くだけ,とする。JavaScript は別ファイルにせず,HTML 中に <script> タグで記述する。
これは,Qiita の記事でコードを提示するのに 1 ファイルだけのほうが見せやすいから,というだけの理由。自分のためのコードでは別ファイルに分けるつもり。

さて,重大な注意点を。HTML ファイルをブラウザーで直接開いたのでは Stimulus は動かないらしいので,Apache か何かのウェブサーバーを通して HTML にアクセスしなければならない。
Apache なんか無くても,Ruby がインストールされているなら,index.html を置いたディレクトリーで

ruby -run -e httpd .

とやって,http://localhost:8080 でアクセスできる。
ほかのプログラミング言語でやりたければ下記を参照:
ワンライナーWebサーバを集めてみた - Qiita

Stimulus を読み込む

Stimulus を読み込まないことには始まらない。どう書けばよいのか。
この記事では Node.js とかは使わないでやる前提だった。

Stimulus Handbook の Installing Stimulus in Your Application によれば,script タグで stimulus.umd.js を読み込め,とあるので,

<script src="https://unpkg.com/stimulus/dist/stimulus.umd.js"></script>

としてみる。

ちなみに,これで読み込まれる Stimulus はバージョン 2.0.0 だった。
将来,この同じ URL で違うバージョンになるのかどうかは知らん。

最初の試み(失敗)

まずこんなコードを書いてみた。ファイルの一部ではなく全体である(<html> など省略可能なタグは極力略した)

https://stimulus.hotwired.dev/ のページのコードをかなり素直に持ってきたつもり。
でも動かない。

<!DOCTYPE html>
<meta charset="utf-8">
<title>Stimulus Test</title>
<script src="https://unpkg.com/stimulus/dist/stimulus.umd.js"></script>
<script>
  import { Controller } from "stimulus"

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

    greet() {
      this.outputTarget.textContent =
        `Hello, ${this.nameTarget.value}!`
    }
  }
</script>
<div data-controller="hello">
  <input data-hello-target="name" type="text">
  <button data-action="click->hello#greet">Greet</button>
  <span data-hello-target="output">
  </span>
</div>

ウェブコンソールには

SyntaxError: import declarations may only appear at top level of a module

というエラーが出ている。
えっ? 「import はモジュールのトップレベルでないとアカン」てどういうこと?

そもそもモジュールが何なのか分かってないのだが,エラーメッセージでウェブ検索してみたところ,

<script>
  import { Controller } from "stimulus"

  // 中略
</script>

<script> タグに type="module" を付けて

<script type="module">
  import { Controller } from "stimulus"

  // 中略
</script>

とすればよいことが分かった。
こうするとこのスクリプトがモジュールとみなされるらしい。
これが無いと,「モジュールじゃないところで import を使おうとした」として怒られるのだ。

これでエラーは解消した。

しかし,別のエラーが出て動かない。

import が間違ってた

次に現れたエラーは

モジュール指定 “stimulus” の解決時にエラーが発生しました。モジュール指定の相対パスは “./” または “../”, “/” のいずれかで始まらなければなりません。

というもの。
ちょっと何を言ってるかさっぱり分からないんだが。

エラーが出た場所は

import { Controller } from "stimulus"

のところ。
MDN の import を見ると,from のあとに指定するのは

モジュールがある .js ファイルへの相対または絶対パス名です

とのこと2
そうかファイルパスを指定するのか。拡張子の有無とかまあいろいろあるわけだが,何にしても stimulus.js とかその手のファイルを置いてあるわけじゃないのにこんな指定をするのは根本的に間違っていたわけだ。

おそらく,
https://stimulus.hotwired.dev/
のトップページのコードで

import { Controller } from "stimulus"

とやっているのは,Webpack か何か知らんけど,なんかそういうやつで手元に stimulus.js か何か知らんけど,そういうファイルが出来ている,ということなんだろうな。

いま目指しているコードでは,

<script src="https://unpkg.com/stimulus/dist/stimulus.umd.js"></script>

で Stimulus 自体はもう読み込んでいるので,import で stimulus を読もうとするのは全くの筋違いだったのだ。
上のコードだけで Stimulus というオブジェクトは出来るらしい。

で,試しに

import { Controller } from "stimulus"

を削除してみたら

Uncaught ReferenceError: Controller is not defined

というエラーが出た。まあ,そりゃそうだよな。
では Controller とやらはどうやれば使えるようになるのか?

正しいコード

何をどうすればいいのかさっぱり分からず,いろんなページを参照してどうにか辿り着いたのが以下のコード。
これで完全に期待どおりに動作する。

<!DOCTYPE html>
<meta charset="utf-8">
<title>Stimulus Test</title>
<script src="https://unpkg.com/stimulus/dist/stimulus.umd.js"></script>
<script type="module">
  const Application = Stimulus.Application
  const Controller = Stimulus.Controller
  let app = Application.start()

  app.register("hello", class extends Controller {
    static targets = [ "name", "output" ]

    greet() {
      this.outputTarget.textContent =
        `Hello, ${this.nameTarget.value}!`
    }
  })
</script>
<div data-controller="hello">
  <input data-hello-target="name" type="text">
  <button data-action="click->hello#greet">Greet</button>
  <span data-hello-target="output">
  </span>
</div>

これを解説する能力は私には無いが,理解できたことを記してみる。
Controller は,Stimulus.Controller の形で得られるようだ。繰り返すが,stimulus.umd.js を読み込んだ時点で Stimulus というオブジェクトはもう使える状態になっている。
あとで簡単に使えるように

const Controller = Stimulus.Controller

として Controller という定数に代入しておく。

しかし,Controller だけではダメなようだ。
どうやら,アプリケーション(?)というものを生成して,そいつにコントローラーを登録してやらなければならないらしい(自分でも何を言っているのか意味不明なんだが)。
それが

const Application = Stimulus.Application

let app = Application.start()

app.register("hello", ナントカカントカ)

というコード。
まず,Application だが,これは複数箇所で使うわけでもないし,べつに定数を定義しなくてもいきなり

let app = Stimulus.Application.start()

でいいはず。というか,実際そう書き換えてみて大丈夫だった。

えーと,なんで const じゃなくて let なんだろう? どのサイトを参考にしたか既に忘れたけど,app に再代入することはなさそうなので,const でいいと思うのだが。実際 const に書き換えて問題なかった。

次に

app.register("hello", ナントカカントカ)

だが,register3 はその名のとおり,アプリケーション(?)にコントローラーを登録するもののようだ。

register の第二引数は

class extends Controller {
  // 云々
}

という形をしている。これを理解するためには JavaScript のクラスを理解しなければならないが,とりあえず今は Stimulus の使い方が知りたいだけなので,スルー4

気になるのは第一引数の "hello" だ。
いや,そもそも「コントローラーを登録する」というのが,何のことかさっぱり分からないんだが,それはともかく,非常に大事なことは「register の第一引数はコントローラー名である」ということ。

コントローラー名は,HTML 側で使われている。つまり,

<div data-controller="hello">
  <input data-hello-target="name" type="text">
  <button data-action="click->hello#greet">Greet</button>
  <span data-hello-target="output">
  </span>
</div>

の部分。
まず,適用すべきコントローラーの名前を一番上の要素で data-controller="hello" として指定している。
だから,ここで指定する名前と register の第一引数は一致していなければならない。
これが食い違っていると何も起こらない。とくにエラーとかも出ない。

コントローラー名が出てくるのはここだけじゃない。
Stimulus には「ターゲット」という概念が出てくるが,ターゲット要素(という呼び方でいいのか?)の指定で,

<input data-hello-target="name" type="text">

のように,data-[コントローラー名]-target という属性が使われる。
さらに,アクションを指定する要素では

data-action="click->hello#greet"

のように,属性値が [イベント名]->[コントローラー名]#[メソッド名] という形をしている。

とにもかくにも,以上で Stimulus の最低限の使い方が分かった。
これで一応目標には到達したが,もう少し調べを進めよう。

ダウンロードした Stimulus のコードを使う

上記で完成を見たコードは

<script src="https://unpkg.com/stimulus/dist/stimulus.umd.js"></script>

で外部サイトから Stimulus を読み込んでいた。
これだとオフラインでは動作しない(実際,この記事を書くために外出先で動作確認をする際,ネット接続できない場所があったりして困った)。
それに,なんか非互換なバージョンアップとかあったりしないのか,ちょっと不安。

そこで,
https://unpkg.com/stimulus/dist/stimulus.umd.js
にアクセスして(リダイレクトされる),ファイルをローカルに保存し,<script> 要素でそいつを読み込むように変更したが,とくに問題なかった。

Internet Explorer で動くか?

Stimulus 自体は,バージョン 2.0.05 では IE 11 にも対応しているらしいが,動かなかった。何が原因かは分からない。

ちなみに,今回のコードでは import を使わなかったが,IE ではこれが使えない。
参考:import - JavaScript | MDN

別の書き方

「正しいコード」の節で書いたコードは,以下のように書いてもよいようだ。

<!DOCTYPE html>
<meta charset="utf-8">
<title>Stimulus Test</title>
<script type="module">
  import { Application, Controller } from "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js"
  window.Stimulus = Application.start()

  Stimulus.register("hello", class extends Controller {
    static targets = [ "name", "output" ]

    greet() {
      this.outputTarget.textContent =
        `Hello, ${this.nameTarget.value}!`
    }
  })
</script>
<div data-controller="hello">
  <input data-hello-target="name" type="text">
  <button data-action="click->hello#greet">Greet</button>
  <span data-hello-target="output">
  </span>
</div>

若干シンプルになって好ましい。

違う点は以下のとおり。

  • Stimulus を <script> タグでなく import で読み込んでいる。
  • これにより ApplicationController を作っている。
  • URL が違っている
  • Application.start() の返り値を定数 app でなく window.Stimulus に代入。

まず,import について。そうか,from に指定するのは同じサイト内のファイルパスでなくて https:// で始まる URL でもいいわけだね。

Stimulus の URL だが,
https://unpkg.com/stimulus/dist/stimulus.umd.js
 ↓
https://unpkg.com/@hotwired/stimulus/dist/stimulus.js
となった。
2021/09/24 時点で,前者は Stimulus 2.0.0,後者は Stimulus 3.0.0-rc.1 が読み込まれた。
(2021/09/25 追記)これを書いた直後にバージョン 3.0.0 がリリースされた。

バージョン 2 と 3 の違いは大きいなあ。
まあ,今は学習の段階だし,実サイトで使う頃には 3.0 がリリースされているだろうから,もうバージョン 3 でやっていくか。
3.0.0-rc.1 てことは 3.0.0 のリリース候補版だからそう大きくは変わらないだろうし。
バージョン 3 は,もはや Internet Explorer をサポートしないそうだけど,いいです,それで。
(2021/09/25 追記)上に追記したとおり,すでに 3.0.0 は正式版が出ているので,いまから学習するならこのバージョンでしょう。

なお,上記のコードは Stimulus の Handbook の Using Without a Build System を参考にした。

えっと,「正しいコード」の節のコードに書いた Stimulus と,この節のコードに書いた Stimulus は全く別物のようなので要注意。

おわりに

いきなり stimulus-rails とか使うと,何がなんやらワケが分からんことになりそうなので,まずは純粋に Stimulus そのものを理解するために,最小のコードで Stimulus を動かしてみた。
Webpack とか何かそういう大がかなりな(?)仕組みを一切排除して動かせることが分かった。

JavaScript に関して,モジュールだのクラスだの extends だの export だの,イマドキの概念について,まず何を学ぶべきかも見えてきた。

しばらく静的サイトで Stimulus とイマドキの JavaScript の学習を続けてみて,よく理解できたら Rails とかでも使ってみようと思う。

  1. なんで Stimulus の記事なのに「Ruby」なのかと言えば,単に筆者が Ruby 好きだから。

  2. 英語版では「This is often a relative or absolute path name to the .js file containing the module.」と「often」が入っていて若干ニュアンスが異なる。

  3. 余談だが,register という単語は regist という動詞(そんなものは無い)に -er を付けたものではなく,register という動詞の語尾がたまたま er だっただけである。

  4. そのうち勉強します。そのうち,な。

  5. 2021/09/23 時点での最新版が Stimulus 2.0.0。

28
11
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
28
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?