Stimulus v3.0.0で追加されそうな機能

先日(執筆時点では今朝未明)にStimulusのv3.0.0-beta.1が発表されました。

<blockquote class="twitter-tweet"><p lang="in" dir="ltr">Stimulus v3.0.0-beta.1. <a href="https://t.co/1f8QwKhY1e">https://t.co/1f8QwKhY1e</a> <a href="https://t.co/yOBWrx1oOx">pic.twitter.com/yOBWrx1oOx</a></p>&mdash; DHH (@dhh) <a href="https://twitter.com/dhh/status/1432367843035820033?ref_src=twsrc%5Etfw">August 30, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

以前[DHHのブログ](https://world.hey.com/dhh/bringing-hotwire-to-basecamp-91a442d6#:~:text=Stimulus%20is%20also%20due%20for%20a%202.1%20release%20in%20the%20near%20future,%20which%20will%20include%20a%20helpful%20debug%20mode,%20previous%20value%20passing%20on%20the%20ValueChanged%20callbacks,%20default%20values,%20and%20a%20few%20other%20overdue%20enhancements.)ではStimulus2.1がもう直ぐ出るよとのアナウンスがありましたが、なぜかいきなり3.0.0 beta.1の発表ってことになっててややびっくりです😲

> Stimulus is also due for a 2.1 release in the near future, which will include a helpful debug mode, previous value passing on the ValueChanged callbacks, default values, and a few other overdue enhancements.

最近は色々ありましたがバージョンアップに到れたのは素直に嬉しいですね。
界隈が待望してた機能も多く含まれてると思います！

発表された機能やオプションはそこまで多くもないので一つずつ拾ってみます😋

## npmパッケージが`stimulus`から`@hotwired/stimulus`になりました。

リポジトリがhotwired orgに移行したのに伴い、パッケージも`@hotwired/stimulus`になりました。
skypack等で参照してる場合は読み込むurlが変わることになりますのでお気をつけください。

今回のパッケージから必要となるすべてのパッケージが組み込まれており、これまで`@stimulus/polyfills`を個別で読み込んでいたものが不要になりました。

## アクションのメソッドにパラメータが渡せるようになりました。

アクションにHTML側からパラメータが渡したいけど渡せないということについては長年様々な場所で議論されていましたが、とうとう公式にサポートされることになりました。

HTML側に`data-action`属性とともに、`data-{controller}-{name}-param="1234"`の形式で属性を付与すると、アクションの発火時にパラメータとして渡されます。

パラメータはeventオブジェクトに`params`キーが追加される形で渡ってくるようです。

```html
<button type="button"
  data-action="click->a#method1"
  data-a-hoge-param="1234"
>Fire method1</button>
```

```javascript
app.register('a', class extends Controller {
  method1({params}) {
    console.log(params)
  }
});
```

<p class="codepen" data-height="450" data-theme-id="dark" data-default-tab="js,result" data-slug-hash="MWoKzMg" data-user="nazomikan" style="height: 450px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/nazomikan/pen/MWoKzMg">
  QIITA用_書き捨て</a> by nazomikan (<a href="https://codepen.io/nazomikan">@nazomikan</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

型のキャストは以下の対応を取ります。

| Data attribute | Param | Type |
| --- | --- | ---|
| data-item-id-param="12345"| 12345 | Number |
| data-item-url-param="/votes" | "/votes" | String |
| data-item-payload-param='{"value":"1234567"}'| { value: 1234567 } | Object |
| data-item-active-param="true"	| true | Boolean |

どうやら、ざっくり`JSON.parse`してるようですね [[差分](https://github.com/hotwired/stimulus/pull/249/files#diff-6cf02ff514db8011d0e55e5118a022a9942eaa23ecdd37f21c9bf18e03137937R81-R87)]

## TargetObserverが実装されました。

targetプロパティがMutationObserverで監視され、その生死にあわせて発火するコールバックが追加されました。

targetで指定された要素が追加された(あるいは既存の要素にtargetの属性が付与された)場合に`{name}TargetConnected`が、逆に削除された(あるいはtarget属性が外れた)場合に`{name}TargetDisconnected`が発火するようになったようです。

これはいくつか嬉しいユースケースがあります(特に以下に限った話ではないのですが)

要素が削除された時「〜をしたい」といったケースってイベントによる実装が難しかったりするのです。

理由は簡単で、削除された要素はDOM中にその時点でいないのでイベントをバブルアップさせることができないからですね。

これまでこのようなケースを自前のMutationObserverによる監視で補う実装パターンが界隈で多く見られましたが、これらの置き換えをこの`{name}TargetDisconnected`で実現できるようになるわけです。

具体的に比較コードを用意しました。

以下は要素のカウントを数えるコントローラです。

要素が削除されるごとにカウントを更新したい場合これまでは以下の実装パターンが界隈で多く見られました。

<p class="codepen" data-height="450" data-theme-id="dark" data-default-tab="js,result" data-slug-hash="VwWeqey" data-user="nazomikan" style="height: 450px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/nazomikan/pen/VwWeqey">
  QIITA用_書き捨て</a> by nazomikan (<a href="https://codepen.io/nazomikan">@nazomikan</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

```javascript
connect() {
  let observer = new MutationObserver(() => this.updateCount());
  observer.observe(this.element, {childList: true})
  //...
}
```

この部分ですね

`{name}TargetDisconnected`を用いると、以下の例のようにわざわざこのMutationObserverを呼び出す処理を自前で書く必要はなくなります。

<p class="codepen" data-height="450" data-theme-id="dark" data-default-tab="js,result" data-slug-hash="bGREOqy" data-user="nazomikan" style="height: 450px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/nazomikan/pen/bGREOqy">
  QIITA用_書き捨て</a> by nazomikan (<a href="https://codepen.io/nazomikan">@nazomikan</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>




## Values APIにデフォルト値が設定できるようになりました。

従来のvaluesは、JavaScript側で宣言する時にキー名とタイプを表現することしかできませんでしたが、今回から値部分をオブジェクト表現することで、デフォルト値を設定することができるようになりました。

従来の指定

```javascript
static values = {name: String, score: Number}
```

今回からの指定(従来通りの指定も可能)

```javascript
static values = {name: String, score: {type: Number, default: 0}}
```

<p class="codepen" data-height="350" data-theme-id="dark" data-default-tab="js,result" data-slug-hash="vYZLvdz" data-user="nazomikan" style="height: 350px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/nazomikan/pen/vYZLvdz">
  QIITA用_書き捨て</a> by nazomikan (<a href="https://codepen.io/nazomikan">@nazomikan</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## ValueChangedコールバックにPreviousValueが渡るようになった

こちらはリリース文書には直接記載はありませんが、`xxxValueChanged`コールバックに引数が二つ渡るようになりました。

第一引数は現在値、第二引数が前回値です。

直前の値を確認したいという要求はちらほら聞かれていましたがそれが今回から可能になったという感じです。

本件については[v3.0.0-beta.1](https://github.com/hotwired/stimulus/blob/main/docs/reference/values.md#previous-values)のドキュメント内で言及があります。

## Classes APIにマルチクラスアクセサのxxxClassesが実装されました。

これはUtility-first CSSのブームに伴い、求める声が高まっていった機能です。

今まで通りの属性指定で、値を複数渡します。

`data-{controller}-{name}-class="class1 class2 class3"`

そうした時に、JavaScript側で`this.{name}Classes`でアクセスするとそれを配列形式で受け取ることができるようになります。

Utility-first CSSとかの場合、一気にn個のクラスを付け替えたいというケースは往々にしてあります。

これはそういった場面に役立ちます。

<p class="codepen" data-height="400" data-theme-id="dark" data-default-tab="js,result" data-slug-hash="gORPZKE" data-user="nazomikan" style="height: 400px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/nazomikan/pen/gORPZKE">
  QIITA用_書き捨て</a> by nazomikan (<a href="https://codepen.io/nazomikan">@nazomikan</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## Debugモードが導入されました。

開発中、ページにどんなコントローラが埋め込まれており、どんなアクションが発火したのかしばしば可視化できたら嬉しいという場面はあるかと思います。
そういった開発を促進するための情報をコンソールに吐き出してくれるオプションが追加されました。

applicationインスタンスのdebugプロパティをtrueにすると動き出します。

```javascript
const app = Application.start();
app.debug = true
```

[![コンソールにデバッグ情報がでている](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12806/9161dc1b-9d4f-b7ab-79a1-608257a02428.png)](https://codepen.io/nazomikan/pen/bGREOJd)

## ~~未知のコントローラやアクション、ターゲットにたいしてwarningが出る様になりました。~~

~~以下のパターンに該当する時、コンソールにwarningが出力されるようになりました。~~

* ~~data-controller 属性で宣言されたコントローラが存在しないか、アプリケーションに登録されていない場合~~
* ~~data-action属性で宣言されたメソッドが、指定されたコントローラに存在しない場合~~
* ~~data-action属性で宣言されたコントローラが存在しない場合~~
* ~~data-targetまたはdata-[identifier]-targetで宣言されたターゲットが、コントローラのstatic targets配列に存在しない場合~~
* ~~data-targetで宣言されたコントローラが存在しない場合~~
* ~~data-[identifier]-target 属性が空の場合~~
* ~~data-target属性のフォーマットが間違っているか、値が空の場合~~

![コンソールにcontrollerの参照がないことを指摘するwarningがでている](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12806/2263eca1-5d13-24e9-59f0-e336dcf2dd14.png)

~~これはapplicationインスタンスのwarningsプロパティをfalseにすることで出力を抑制することができます。~~

```javascript
const app = Application.start();
app.warnings = false
```

**2021/09/23追記**

ついさきほど根本的な問題があったということで一旦revertされました。
表現の仕方的に復活がないというわけではなさそうですが、3系に含まれるかは黄色信号という感じになりました。

https://github.com/hotwired/stimulus/pull/450

コントローラをネストした時の挙動に問題があるという報告があったほか、本日あらたに採用された`shouldLoad`との共存が難しかったためではないかなと推測します。

このrevertは先ほどマージされたのでv3.0.0-rc.1のタイミングでなくなる見込みです。

## コントローラにshouldLoadフラグが追加されました (2021/09/23追記)

コントローラの読み込みを制御するためのshouldLoadフラグが追加されました。
コントローラに静的プロパティとしてshouldLoadを定義し、falseに設定するとそのコントローラが読み込まれなくなります。

```javascript
import { isWebView } from "../browser"

app.register('foo', class extends Controller {
  static shouldLoad = !isWebView(); // webviewの時にこのコントローラを読まれないようにする
})
```

これはやや奇妙な感じを抱くかもしれません。 

なぜならテンプレート側でコントローラ属性の付与を制御すればこのプロパティはそもそも不要となるはずだからです。

これはHTML自体をキャッシュしたいようなケースを想定してJavaScript側だけで読み込みを制御できるように用意されたもののようです。

すでにマージされたのでv3.0.0-rc.1のタイミングでリリースされる見込みです。


https://github.com/hotwired/stimulus/pull/448


## コントローラにdispatch関数が実装されました。

Stimulusではコントローラ間の連携にイベントを利用することが推奨されるケースが多々あります。
その際、都度手動でイベント生成、発火までを行う煩わしさを軽減するために、`dispatch`関数が用意されました。

これまで

```javascript
open() {
  let evt = new CustomEvent('disclosure:open', {bubbles: true});
  this.element.dispatchEvent(evt);
}
```

のように書いていたものが

```javascript
open() {
  this.dispatch('open')
}
```

で実現できます。 詳細な引数情報は以下に示します。

`this.dispatch(eventName, option)`

| 引数 | type | 説明 |
| --- | --- | --- |
| eventName | String | 発生させるイベント名<br /> 第二引数のoptionでprefixが明示的に指定されていなければ`コントローラ名:eventName`というイベント名になる|t
| option | Object | prefix: イベント名のprefix (default: コントローラ名 = `this.identifier`) <br /> target: イベントを発生させる要素 (default: コントローラ要素 = `this.element`) <br /> cancelable: キャンセル可能かどうか (default: `true`) <br /> bubbles: バブルアップさせるかどうか (default: `true`) <br /> detail: イベントに渡すデータ (default: `{}`) |

と、ここまで記述しましたが、実際に手元でためしてみると`this.dispatch()`では動作しませんでした。
`this.context.dispatch()`だと期待通り振る舞うみたいですが、PRでの説明やリリース文書と乖離があるのでこちらは現在issueで聞いてる次第です。(私の勘違いだったらゴメンナサイ)

https://github.com/hotwired/stimulus/issues/431

**9/1追記**
どうやら期待した振る舞いではなかったようで、修正のPRが作られました。
`this.dispatch()`が想定してたもので間違いないようです。

https://github.com/hotwired/stimulus/pull/432

**9/14追記**
beta2で修正がリリースされたようです。

https://github.com/hotwired/stimulus/releases/tag/v3.0.0-beta.2

## IE11のサポートが終了

StimulusのtsconfigのtargetがES5からES2017に変更されており、確認してみたところStimulus3系からはIE11のサポートが打ち切られることになったようです。
本件は[3.0.0-beta.1](https://github.com/hotwired/stimulus/blob/main/docs/handbook/07_installing_stimulus.md#browser-support)のドキュメント内でも確認できます
確認した際に[リリース文書](https://github.com/hotwired/stimulus/releases/tag/v3.0.0-beta.1)にもその旨の追記していただけたみたいです。

引き続きIE11をサポートするケースにおいてはStimulus2 x @stimulus/polyfillsの組み合わせを使ってくれということみたいです。