如何使用 Turbo、ImportMaps 和 Stimulus 在 Rails 7 应用程序中加载页面特定的自定义 Javascript 函数?

Tin*_*n81 3 ruby stimulusjs import-maps turbo ruby-on-rails-7

我刚刚将 Rails 6 应用程序更新到 Rails 7,我发现很难将旧的“Vanilla Javascript”代码挂接到 Rails 的TurboStimulus和 ImportMaps 新设置中。

为了使事情变得更容易,我简单地rails new NewAppName在命令行上运行,然后将旧的遗留文件一点一点迁移到新应用程序中。


ImportMap 是由 Rails 为我生成的。我只添加了最后一行来包含我的旧自定义 Javascript 文件。

配置/导入映射.rb:

# Pin npm packages by running ./bin/importmap

pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"

pin_all_from "app/javascript/components", under: "components" # these are the custom JS files from my old Rails 6 app
Run Code Online (Sandbox Code Playgroud)

应用程序/视图/布局/application.html.erb:

<head>
  ...
  <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
  <%= javascript_importmap_tags %>
  <%= yield :head %><!-- load page specific custom JS here -->
  ...
</head>
Run Code Online (Sandbox Code Playgroud)

这是我的旧应用程序中的典型 JS 文件。

应用程序/javascript/components/table_row.js:

function TableRow() {

  const rows = document.querySelectorAll('table tr[data-url]');

  rows.forEach(row => {
    row.addEventListener('click', function(e) {
      handleClick(row, e);
    });
  });

  function handleClick(row, e) {
    const url = row.dataset.url;
    window.document.location = url;
  }

}

document.addEventListener('turbo:load', TableRow);
Run Code Online (Sandbox Code Playgroud)

我在所有 JS 文件中替换了DOMContentLoadedturbo:load使其与 Turbo 一起工作(希望这是正确的?)。


这就是我在某些页面上注入特定于页面的 JS 的方法:

应用程序/视图/引用/index.html.erb

<% content_for(:head) do %>
  <%= javascript_import_module_tag "components/table_row" %>
<% end %>
Run Code Online (Sandbox Code Playgroud)

现在的问题是,所有这些都以某种方式起作用,但有时却不起作用,而且不是很可靠。例如,table_row.js文件应该加载到索引页上,但有时它没有加载,我真的不明白为什么。

我怀疑我以某种方式错误配置了 Turbo 或 Stimulus 或我的 ImportMap,但真的不知道在哪里。

有人能告诉我我在这里缺少什么吗?

Max*_*nce 5

我没有使用Importmaps,但我宁愿在后端捆绑并使用jsbundlingesbuild以良好的旧方式提供我的静态资产Sprockets。所以我会尽量不说一些不适用于你的情况的话。

但我看到的第一件事是你正在 Rails 6 中初始化你的sprinkles,DOMContentLoaded这基本上意味着你没有使用Turbolinks. 然后使用Turbo现在更新的版本会破坏你的 JS。

什么是Turbo?Turbo 是一种让你的资源加载更加高效的尝试,因为head你的 DOM 在重新加载之间基本上保持不变 => 你的 JS 在页面之间保持不变。(虽然你可以强制重新加载头部,但我不会讨论这个)。除了提高效率之外,它还使您的页面看起来更加流畅。

如果您添加stimulus并利用它Turbo framesTurbo streams您就可以拥有一个非常高效的单页应用程序。是的,基本上 Rails 7 确实进入了 SPA 领域。(但是前端比react其他框架更简单或更先进angular

好吧,现在我看到了什么:

您的洒水function TableRow()是在助手的帮助下添加到<head></head>content_for

这里有两件事:

  • 如果您访问的第一个页面有TableRow()in:head那么很好,它将包含在您应用程序的 Javascript 中。虽然它不会在页面之间重播。只是因为document.addEventListener('turbo:load', TableRow);也包含在其中,然后只会在第一次加载时播放。
  • 如果您访问的第一个页面没有TableRow()in :head,那么如果您稍后访问具有正确帮助程序的页面,我什至不确定它是否会包含在 Javascript 中content_for。只是因为 Javascript 在页面之间没有改变。

这可能就是您看到不一致结果的原因。

解决方案是强制 Turbo 在每次访问新页面时重新加载 Javascript(就像旧的 Rails 方式一样)。turbo但说实话,你这样做有点扼杀了所有的好处。

现在有解决方案

如果您使用 Stimulus,那么您可以解决所有问题。Stimulus 是一个非常棒的小型库,可以轻松地将任何洒水附加到您的 DOM 上。

因此,您可以创建一个 Stimulus 控制器,并TableRow()在 Stimulus 控制器内部添加您的connect()方法。connect 方法是每次控制器附加到 DOM 元素(并且该 DOM 元素附加到页面)时播放的一些 Javascript。所以基本上所有的 Javascript 都是静态的,不会随着页面的不同而改变。但是,当 Stimulus 看到特定控制器的挂钩时,它会触发该connect()方法并播放TableRow()其中的内容。

基本上,在过去,Javascript 每次加载到页面上时都会播放一次并永久播放。现在它基本上是跨页面永久的,并且一些 Stimulus hooks 在需要时播放该 Javascript。

我不会Stimulus在这里讨论代码,因为这个答案可能看起来相当臃肿,但现在你得到了刺激/涡轮的图片。