Tech Racho エンジニアの「?」を「!」に。
  • Ruby / Rails関連

Stimulusを強化するStimulus-FXにデバッグ機能を追加しました(翻訳)

概要

元サイトの許諾を得て翻訳・公開いたします。

日本語タイトルは内容に即したものにしました。

Rails-Designer/stimulus-fx - GitHub

Stimulusを強化するStimulus-FXにデバッグ機能を追加しました(翻訳)

Stimulusには基本的なデバッグモードが備わっていて、これを用いればStimulusが動いているかどうかや、どのコントローラが初期化・接続されているかを表示できます。
しかしStimulusコントローラでの処理が増えてくると、もっと詳しい情報が必要になりますが、そのときにDOMとコントローラのロジックをいちいち手動で比較せずに済ませたいものです。

そこで、新しいデバッグ方法を模索した結果、私が最近公開したStimulus FXというツールにenableDebugという新しい「FX」を追加しました。
これを使うと、以下のようにブラウザコンソールのデバッグ情報をより詳しく表示できます。

インストール後のセットアップは簡単です。

// app/javascript/controllers/application.js

+import { enableDebug } from "stimulus-fx"

-const application = Application.start()

+const application = enableDebug(Application.start())

上のセットアップが終わったら、デバッグを有効にしたいStimulusコントローラごとに以下を追加します。

export default class extends Controller {
+  static debug = true
+
// ...
}

このデバッグ機能は、実現可能性をチェックするための実験的機能として追加したものです。本機能のリリースによって、どなたかがこの機能をさらに自分で拡張するきっかけになれば幸いです。私自身もいくつかのアイデアを暖めていますが、皆さんに影響を与えるつもりはありません。

🔗 しくみ

このデバッグ機能のコードは驚くほどシンプルです(#8)。このコードは、applicationを引数として受け取るenableDebugという関数を作成しています(enableDebug(Application.start())という使い方を見ればおわかりでしょう)。

import { debuggable } from "./debuggable";
import { initialize } from "./enableDebug/initialize";
import { values } from "./enableDebug/values";
import { targets } from "./enableDebug/targets";

export function enableDebug(application) {
  const debugFeatures = [
    initialize,
    targets,
    values
  ];

  return debuggable(application, { with: debugFeatures });
}

読みやすいコードですよね?「(デバッグ)機能を持つデバッグ可能なアプリケーションを返す」コードであることは一目瞭然です。

次のdebuggable関数は、指定の機能をfeatures.forEach(feature => feature(identifier, callbacks));でループし、識別子とコールバックを渡してその機能を呼び出します。
debuggableinitialize関数は以下のようにシンプルです。

export function initialize(identifier, callbacks) {
  const debugCallback = ({ for: lifecycle }) => ({
    log(context) {
      console.group(`#${lifecycle}`);

      console.log("details:", {
        application: context.application,
        identifier,
        controller: context,
        element: context.element
      });

      console.groupEnd();
    }
  });

  ["initialize", "connect", "disconnect"].forEach(lifecycle => {
    callbacks[lifecycle].push(function() {
      debugCallback({ for: lifecycle }).log(this);
    });
  });
}

このコードの読みやすさについても強調しておきたいと思います。「for: lifecycleを指定してデバッグコールバック(debugCallback)を呼び出している」のがおわかりですよね?
この構文は、私の近著『JavaScript for Rails Developers』全体を通じて提唱している方法を体現しています。

values関数とtargets関数はもう少しだけ複雑です。まずはsrc/enableDebug/values.jsのコードを見てみましょう。

export function values(identifier, callbacks) {
  callbacks.connect.push(function() {
    logValues({ on: this.element, for: identifier });
  });
}

function logValues({ on: element, for: identifier }) {
  const values = allValues(element, identifier);

  if (Object.keys(values).length === 0) return;

  console.group("Values");
  console.table(values);
  console.groupEnd();
}

function allValues(element, identifier) {
  const prefix = `${identifier}-`;
  const dataPrefix = "data-";
  const valueSuffix = "-value";

  // 要素や識別子から値を取得するロジックをここに書く
}

values関数はlogValuesからの結果をpushしています。src/debuggable.jsで定義されているconnect配列には、console.tableのグループ化の結果が置かれます(targets関数についても同様です)。

export function debuggable(application, { with: features }) {
  // ...
  const callbacks = {
    initialize: [],
    connect: [],
    disconnect: []
  };
  // ...
}

src/debuggable.jsにあるこれらの関数は、続いてStimulusコントローラのconnect(および他のライフサイクルメソッド)内で呼び出されます。

export function debuggable(application, { with: features }) {
  // ...

  controller.prototype.connect = function() {
    callbacks.connect.forEach(hook => hook.call(this));

    // ...
  };
}

以上で解説はおしまいです。
本記事の簡単なツアーが、stimulus-fxの機能を自分で拡張してみたい方にお役に立てば幸いです。このデバッグ機能が多くの人に支持されて関心が深まれば、Stimulus本体に移植されるかもしれません。

不明な点がありましたら、お気軽にお問い合わせください

関連記事

Stimulusで知っておきたい10の機能(翻訳)

Stimulus.jsコントローラ間の癒着を防ぎながらイベントを中継する方法(翻訳)


CONTACT

TechRachoでは、パートナーシップをご検討いただける方からの
ご連絡をお待ちしております。ぜひお気軽にご意見・ご相談ください。