PhoenixでPage Specific JavaScript
と言ってもすでにインタネットに情報があります。
まずはviewモジュール名(@view_module)とテンプレート名(@view_template)からjsファイルのパスが導出できるように取り決めしておき、bodyタグのdata属性にそのパスを吐き出しておく。で、DOMContentLoadedイベントでそのパスを読み、webpackのrequire.contextでガバっと読み込んだ中から実行するものを決める、という寸法。
ただこのページにあるコードではjsのファイル名がイマイチで、もうちょい良くなりそう。
def unique_view_name(view_module, view_template) do [_elixir, _app | context_controller_template] = view_module |> Phoenix.Naming.humanize() |> String.split(".") [action, "html"] = view_template |> String.split(".") Enum.join(context_controller_template ++ [action], "_") end
例えばUserControllerのshowアクションとかなら、userview_showになるし、Admin.UserControllerのindexアクションとかなら、admin_userview_indexになる。ただしこれだとjsファイルを同じディレクトリに全部並べなくてはならず、Elixirのコードと同じような階層構造をつくって整理できない。また、jsのファイル名はkebab-caseかCamelCaseがおそらく一般的で、少なくとも_区切りは好まれない。あとuserviewのようにviewが全部に付くのもどうにかしたい。
ということで書いてみたのが以下。
def js_module_path(view_module, view_template) do [_elixir, _my_app_web | context_view] = view_module |> Atom.to_string() |> String.split(".") context_names = context_view |> List.delete_at(-1) resource_name = view_module |> Phoenix.Naming.resource_name("View") template_name = view_template |> String.replace_suffix(".html", "") (context_names ++ [resource_name, template_name]) |> Enum.map(&(&1 |> Phoenix.Naming.underscore |> String.replace("_", "-"))) |> Enum.join("/") end
Phoenix.Naming便利。これならマックス意地悪な入力を与えても
js_module_path( MyAppWeb.GreatShop.TopSeller.FancyItemView, "member_only_index.html" ) #=> "great-shop/top-seller/fancy-item/member-only-index"
という出力になっていい感じ。
ちなみに、これまでPhoenixのモジュールバンドラはbrunchがデフォルトでしたが、v1.4.0からwebpackになります。
全部書いておく。
<!-- app.html.eex --> <html> ... <body data-js-path="<%= js_module_path(@view_module, @view_template) %>"> ... </body> </html>
# lib/my_app_web/views/layout_view.ex defmodule MyAppWeb.LayoutView do use MyAppWeb, :view def js_module_path(view_module, view_template) do [_elixir, _my_app_web | context_view] = view_module |> Atom.to_string() |> String.split(".") context_names = context_view |> List.delete_at(-1) resource_name = view_module |> Phoenix.Naming.resource_name("View") template_name = view_template |> String.replace_suffix(".html", "") (context_names ++ [resource_name, template_name]) |> Enum.map(&(&1 |> Phoenix.Naming.underscore |> String.replace("_", "-"))) |> Enum.join("/") end end
// assets/js/app.js
import css from "../css/app.css"
import "phoenix_html"
import pageScript from './pages/index'
window.addEventListener("DOMContentLoaded", () => {
pageScript()
})
// assets/js/pages/index.js
const requireContext = require.context(".", true, /\.js$/)
const modules = {}
requireContext
.keys()
.filter(filename => filename !== './index.js')
.forEach(filename => {
const path = filename.replace('./', '').replace('.js', '')
modules[path] = requireContext(filename)
})
export default function() {
const path = document.querySelector('body').dataset.jsPath
const fun = modules[path]
if (fun && fun.default) {
fun.default()
}
}