はじめに
Ruby on RailsでRails5まで推奨されていたsprockets環境下での開発でgonを使用する際の注意事項をまとめました。
にしてもRailsは直近フロントエンドのフレームワーク選定が非常に難しくなってきてますね...
gonとは?
Controller内で定義した変数をJSでアクセスできるよう勝手にセットしてくれるものです。
たとえば下記のような例では、Controller内でセットした値をビューとして表示する際にJSが即時実行されてコンソール上にgon.messageとgon.userの内容が表示されます。
Controller
class HomeController < ApplicationController
def index
gon.message = "Hello from Rails!"
gon.user = { name: "Taro", email: "taro@example.com" }
end
end
html.erb
<%= include_gon %> <!-- `gon` を JavaScript で使うためのヘルパー -->
<h1>Gon Test</h1>
<p>Check the console!</p>
<script>
console.log("Gon message:", gon.message);
console.log("Gon user:", gon.user);
</script>
概要
バグとの遭遇
クライアント側で動作させたいパーツが多い画面を開発中のこと。
こんなコードを書きました(一部抜粋)
let contentGonManager = {
default__title: "",
default__body: "",
initialize() {
this.default_title = gon.default_title || "";
this.default_body = gon.default_body || "";
},
};
//その他DOM操作時の動きをトリガーするfunction等
$(document).ready(function () {
contentGonManager.initialize();
//ここでトリガーするfunctionを書く
});
上記、このJSで管理すべき対象のgonを明示する、DOMのレンダリングが完了してからそのgonを受け取ることを保証させたいという意図があります。gonも結局HTML要素に情報ねじ込んでいるだけなので、DOMがちゃんと用意できた後に読み取らせたい。
そこで画面遷移を行うとこんなエラーが
Identifier has already been declared
あれ...?
原因:turbolinksが悪かった
turboinksとは、Rails環境でページ遷移の高速化のために使用されますが、こいつが悪さしていました。
当初、各リクエストごとにHTMLとそのアセットを取得すると思っていました。
画面:取得する内容
A画面:A画面のHTML,JS,CSS
B画面:B画面のHTML,JS,CSS
A画面:A画面のHTML,JS,CSS
しかしturbolinks環境下ではJS,CSSがキャッシュされHTMLの
のみ更新されていたため、JSが同じ実行環境で2回実行されていました。画面:取得する内容
A画面:A画面のHTML,JS,CSS
B画面:B画面のHTML,JS,CSS
A画面:A画面のHTML
となるとJSの実行環境はAから引き継ぐため、2回アクセスした時点でブラウザ上のJSの処理は
//1回目のアクセス
let contentGonManager = {
default__title: "",
default__body: "",
initialize() {
this.default_title = gon.default_title || "";
this.default_body = gon.default_body || "";
},
};
//その他DOM操作時の動きをトリガーするfunction等
$(document).ready(function () {
contentGonManager.initialize();
//ここでトリガーするfunctionを書く
});
//2回目のアクセス
let contentGonManager = {
default__title: "",
default__body: "",
initialize() {
this.default_title = gon.default_title || "";
this.default_body = gon.default_body || "";
},
};
//その他DOM操作時の動きをトリガーするfunction等
$(document).ready(function () {
contentGonManager.initialize();
//ここでトリガーするfunctionを書く
});
のようになっていました。
数珠繋ぎ的に2個実行されてしまってたわけですね。回避策としてはいくつかあります。
①turbolinks:load
イベントを使う
②グローバル変数をwindow
に保存する
③turbolinksのキャッシュを無効化する
④スコープを1リクエスト単位に限定して多重定義を防ぐ。
今回は④の選択肢を取りました。
即時実行関数で閉じ、contentGonManagerが多重定義されないようにしました。
$(function () {
let contentGonManager = {
default_title: "",
default_body: "",
initialize() {
this.default_title = gon.default_title || "";
this.default_body = gon.default_body || "";
},
};
// DOM 操作やイベントのトリガー
function setupTriggers() {
$("#some-button").on("click", function () {
console.log("Button clicked!");
console.log("Default Title:", contentGonManager.default_title);
console.log("Default Body:", contentGonManager.default_body);
});
}
contentGonManager.initialize();
setupTriggers();
});
こうしてあげると1個目のletと2個目のletは別のスコープとして認識されるのでエラーが解消されます。
終わりに
turbolinks使用時にはJS側の変数のスコープ意識してあげないと変なバグを踏むので気をつけたいですね。