发布于 2026-01-06 2 阅读
0

在 Rails 中使用 Hotwire Turbo 和传统 JavaScript DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

在 Rails 中使用 Hotwire Turbo 和旧版 JavaScript

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

Hotwire Turbo2020 年圣诞节前后发布,对我们许多人来说都是个令人兴奋的消息。它的主要吸引力之一在于,它能帮助你在 Rails 中创建高度响应式的网页,而几乎无需编写任何自定义 JavaScript 代码。Turbo 似乎也非常易于使用,它鼓励你尝试并探索你的页面。让我们来看看 Turbo 是否也能用于一个开发时间较长、包含大量旧 JavaScript 代码的项目中(剧透一下:稍作调整,完全可以!)。

在一个长期运行的 Rails 项目中,如何过渡到传统的 JavaScript

多年来,我们见证了 JavaScript 社区将其生态系统发展到如此辉煌的高度,也曾努力(但往往失败)跟上语言增强、新框架和构建系统的步伐。如今, Turbo 的这种简洁性设计令人欣喜。需要说明的是,我们确实喜欢 JavaScript,它是一门优秀的语言,尤其是在 ES6 之后。但我们认为,它的优势只有在团队拥有足够多的、足够专业的 JavaScript 开发人员时才能得以充分发挥。换句话说,对于一个小型 Rails 团队而言,长期管理复杂的 JavaScript 代码可能非常困难。

这就是为什么我们一直谨慎地避免在项目中引入过多的 JavaScript,尤其是一些可以用其他方式实现的功能。不过,在页面响应式开发领域,JavaScript 始终占据着绝对主导地位。大多数人都喜欢响应式页面,我们也一样!所以,最终,我们代码库中还是出现了大量的 JavaScript。

多年来,Rails 对构建响应式 JavaScript 页面的“官方”支持和默认约定经历了许多不同的变化。让我们回顾一下在过去大约 12 年里,我们几乎标准的 Rails 项目中使用的一些 JavaScript 操作选项:

  • 一直以来,都存在着老旧且生锈的内联原生 JavaScript。
  • Prototype库不知从何时起就存在了,但它逐渐被淘汰(大约在 2010 年)。
  • 在 Rails 3.1 中,它被jQuery取代(约 2011 年)
  • Rails 3.1 还引入了CoffeeScript,作为一种新的、受鼓励的“编写 JavaScript”的方式(约 2011 年)。
  • 当时出现了Unobtrusive JavaScript来替代内联样式; jquery-ujs 库(约 2010 年)进一步推动了这一发展,后来又被在某种程度上兼容的 Rails UJS(2016 年)所取代。
  • 2011 年左右出现了服务器生成的 JavaScript 响应(SJR),允许服务器通过 JavaScript 更新页面。
  • 自 Rails 4 起,Turbolinks 库就被引入,但当时(2013 年)存在很多问题。
  • Rails 5 对 Turbolinks 进行了重大且很大程度上不兼容的重写(Turbolinks 5),其先前的版本被更名为Turbolinks Classic(2016)。
  • Rails 5.1可选地采用了webpack打包器yarn 包管理器(2017 年),这两种工具成为 Rails 中处理 JavaScript 的首选方式。
  • Rails 5.1 也从默认依赖项中移除了 jQuery(2017 年)。
  • Stimulus JS框架于2018年发布。
  • 虽然 CoffeeScript 仍然通过gem提供软支持,但不建议使用,而是推荐使用通过 webpack 编译的纯 ES6 JavaScript 或 Typescript(~2018 年)。
  • 经过 3 年的 beta 测试,Sprockets 4于 2019 年发布,支持 ES6 和资源管道中的源映射,以服务那些仍然对 webpack 犹豫不决的人。
  • 最后是Turbo,它应该会成为Rails 7的一部分(2020年底)。
  • 哦,对了,DHH现在正在探索原生 ES6 模块,这可能会让他们放弃 webpacker,重新使用 Sprockets 来处理 JavaScript。
  • 截至 2021 年 8 月的更新:在未来的 Rails 版本中,使用不使用 webpacker 的原生 ES6 模块导入方式来处理 JavaScript 将成为默认方式。

真是一段奇妙的旅程!回想起来,DHH 和其他开发者为了让 JavaScript 生态系统及其各种功能在 Rails 中得以应用,付出了巨大的努力,直到他们找到了一种足够优雅的方式来实现这一点(如果真是这样,我们由衷地感谢他们 🙏)。每一次迭代都意义非凡,每一项新技术的采用都是一次进步,但JavaScript 风格的整体变化仍然非常巨大。虽然根据我们的经验,Rails 本身的升级随着每个版本的更新而变得越来越容易,但我们的 JavaScript 代码却并非如此。几年前 Rails 中的 JavaScript 代码与现在的版本截然不同。

涡轮增压改变了一切

Hotwire Turbo 的出现再次改变了局面,而且这次带来了真正令人振奋的承诺。人们抱有如此高的期望,原因很简单:Turbo 让你无需编写任何 JavaScript 代码即可创建许多响应式页面模式。JavaScript 现在被置于幕后,即使是描述响应式行为,主要关注点也放在了 HTML 上,而 HTML 可以通过 Rails 模板(或其他任何工具)轻松编写。如果你需要与页面进行一些更特殊的交互,自定义 JavaScript 代码(现在最好使用Stimulus JS控制器编写)就只是锦上添花而已。

Basecamp 的新旗舰产品——HEY.com服务——目前总共只使用了约 60kB 的 JavaScript(压缩后),而且就响应速度而言,它感觉就像一个真正的单页应用 (SPA)。相比之下,我们的网站使用的 JavaScript 代码是它的两倍,但基本上只是一个普通的点击加载页面的网站,唉……

所以,有了 Turbo,JavaScript 代码模式过时的问题就彻底解决了,因为将来根本不会有需要升级的自定义 JavaScript 代码

如果一切看起来都那么美好,为什么我们之前一直犹豫不决,迟迟不愿直接添加这个turbo-rails强大的库,踏上这条崭新的道路呢?在真正开始尝试之前,我们有一个很大的顾虑:禁用 Turbo Drive 后,Turbo 还能正常工作吗? Turbo Drive是 Turbolinks 的继任者,也是 Turbo 系列的一员。这个库很棒,但它要求JavaScript 代码以特定的方式组织,这在包含大量遗留 JavaScript 代码的老项目中通常很难实现。我们还没有真正着手进行重构,尽管我们已经接近目标了。在此之前,我们需要确保我们的网站在禁用 Turbo Drive 的情况下也能正常运行。

我们很高兴地发现,这个问题的简短答案是肯定的!如果您想了解更多信息,请继续阅读。

安装涡轮增压器

这里我们就不赘述细节了,官方流程对我们来说正好有效。如果你还在使用 Asset Pipeline 来管理 JavaScript 文件,请确保它支持 ES6 语法(也就是说,你需要升级到Sprockets 4)。此外,你还需要一个足够新的 Rails 版本(似乎是 Rails 6 。除此之外,一切应该都没问题。

不过,这里有个小问题:如果您同时启用了Asset Pipelinewebpack(就像我们一样),并且您只想将 Turbo 包含在 webpack 管理的打包文件中,那么如果您使用了gemturbo.js ,您会发现它也会在 Asset Pipeline 中被预编译。原来,该 gem 会在初始化时自动将此文件添加到管道中。为了避免这种情况(并省去在 Sprockets 中启用 ES6 的一些麻烦),您可以在 Rails 应用启动时将其移除:turbo-rails

# config/application.rb
class Application < Rails::Application
  ...
  # remove Turbo from Asset Pipeline precompilation
  config.after_initialize do
    # use this for turbo-rails version 0.8.2 or later:
    config.assets.precompile -= Turbo::Engine::PRECOMPILE_ASSETS

    # use this for turbo-rails versions 0.7.1 - 0.8.1:
    config.assets.precompile.delete("turbo.js")

    # or use this for previous versions of turbo-rails:
    config.assets.precompile.delete("turbo")
  end
end
Enter fullscreen mode Exit fullscreen mode

请注意,正确的资源名称取决于turbo-railsgem 版本,因此请仅选择其中一行配置。v . 0.8.2 中的此提交添加了一个方便的常量,以便更容易地选择不进行资源预编译。

默认禁用 Turbo 功能

如果您现在尝试浏览您的网站,过一段时间后,您可能会注意到各种故障和异常行为——这是因为 Turbo Drive(Turbolinks)正在大幅提升我们旧版 JavaScript 的性能。我们现在需要做的是默认禁用 Turbo,并仅在需要使用 Turbo Frames 或 Turbo Streams 的地方选择性地启用它。

更新:自 Turbo 7.0.0-rc.2 起,这已成为官方支持的选项,在此之前我们需要一些技巧。

禁用 Turbo 7.0.0-rc.2 或更高版本

从这个版本开始,我们可以通过 JavaScript 包中的这行代码全局启用 Turbo:

// app/javascript/packs/application.js
import { Turbo } from "@hotwired/turbo-rails"
Turbo.session.drive = false
Enter fullscreen mode Exit fullscreen mode

就这样!

禁用旧版本的Turbo

对于我们这些仍然使用 Turbo 6 的用户来说,我们需要采取略有不同的方法。我们将使用一些条件判断的方式来禁用 Turbo,这有助于我们稍后使 JavaScript 代码兼容 Turbo Drive。要在 Rails 的所有页面中完全禁用 Turbo,您可以将以下指令添加到布局文件中:

<%# app/views/layouts/application.html.erb %>
<html>
  <head>
    <% unless @turbo %>
      <meta name="turbo-visit-control" content="reload" />
      <meta name="turbo-cache-control" content="no-cache" />
    <% end %>
    ...
  </head>
  <body data-turbo="<%= @turbo.present? %>">
    ...
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

这里的所有指令都由@turbo变量控制。如果不做任何其他操作,该变量将等于默认值nil,页面将以禁用 Turbo 模式的方式渲染。如果将来有一天,您成功地优化了一组页面的 JavaScript 代码,您可以使用@turbo = true相应的控制器,有选择地为这些页面启用 Turbo 模式(以及 Turbo Drive 功能)。我们很快也将亲自探索这种迁移方法。

具体来说,这些指示的含义是:

  • 最重要的属性是标签data-turbo="false"中的 `<link>` 元素<body>。它告诉 Turbo忽略页面上的所有链接和表单,让浏览器进行标准处理。当 Turbo 决定是否处理链接点击或表单提交事件时,它会在目标元素及其所有父元素中查找data-turbo该属性,如果找到"false"值,则直接放弃处理。这种树遍历机制非常实用,稍后我们将利用它选择性地启用 Turbo,详见下文。

  • 另外两个元标签并非绝对必要,它们起到一种备份作用,以防 Turbo 控制意外“泄露”到其他地方。其中一个turbo-visit-control元标签会强制Turbo 在遇到 AJAX 响应(在 Turbo 框架之外发起)时重新加载整个页面。最后,turbo-cache-control另一个元标签确保页面永远不会被存储在 Turbo 的缓​​存中。

好的,现在您浏览网站时,它的表现应该和您以前习惯的完全一样。

使用Turbo Frames

Turbo Frames就像页面上的可自我替换的块:它们捕获链接点击和表单提交,向服务器发出 AJAX 请求,并将自身替换为从响应中提取的同名 Turbo Frame。

更新:自 Turbo版本 7.2.0data-turbo: true起,不再需要式启用 Turbo Frames 。即使全局禁用 Turbo(驱动),我们仍然可以像往常一样使用 Turbo Frames。请注意,以下段落仅供参考。无论是否启用该属性,代码示例都能正常工作data-turbo

由于我们全局禁用了 Turbo 功能,因此需要针对每个 Turbo 帧选择性地启用它,同样可以使用data-turbo属性来实现,例如:

<%# app/views/comments/show.html.erb %>
<%= turbo_frame_tag @comment, data: { turbo: true } do %>
  <h2><%= @comment.title %></h2>
  <p><%= @comment.content %></p>
  <%= link_to "Edit", edit_comment_path(@comment) %>
<% end %>
...
<%= link_to "Homepage", root_path %>
Enter fullscreen mode Exit fullscreen mode

data-turbo将该属性设置为 true"true"将使 Turbo 处理 Turbo Frame 块内的所有链接和表单,同时忽略框架外的任何链接和表单。因此,在上面的示例中,“编辑”链接将由 Turbo 处理(点击它将渲染一个内联编辑表单),而“首页”链接仍将由浏览器正常处理。

使用 Turbo Stream 响应

Turbo Streams允许后端显式声明要在客户端进行的更改。每当服务器响应包含一个或多个<turbo-stream>元素时,Turbo 都会自动执行其中的操作,并更新页面中相应的片段。

与 Turbo Frames 类似,需要 Turbo Stream 响应的链接或表单必须在启用 Turbo 的上下文中呈现,因此,要使 Streams 正常工作,唯一需要做的更改就是设置该data-turbo属性:

<%# app/views/comments/show.html.erb %>
<div id="<%= dom_id(@comment) %>" data-turbo="true">
  <%= @comment.content %>
  <%= button_to "Approve", approve_comment_path(@comment) %>
</div>
Enter fullscreen mode Exit fullscreen mode

如果服务器以 Turbo Stream 响应(例如通过respond_to代码块)进行响应,Turbo 将执行页面更新命令,如下一个不太美观的示例所示:

# app/controllers/comments_controller.rb
def approve
  ...
  @comment.approve!

  respond_to do |format|
    format.turbo_stream do
      render turbo_stream: turbo_stream.prepend(dom_id(@comment),
                                                "<p>approved!<p>")
    end
  end
end
Enter fullscreen mode Exit fullscreen mode

点击“批准”链接会触发 Turbo(因为它在该上下文中已启用),Turbo 会向服务器发送 AJAX 请求,服务器会返回一个<turbo-stream>包含“prepend”操作的元素,该操作的目标是给定的评论。Turbo 会拦截此响应并执行该操作,从而在评论 div 的前面添加“已批准!”文本。

这一切都只是正常的 Turbo Streams 处理,我们上面只需要为特定的页面片段启用 Turbo 即可。

使用Turbo Streams广播

Turbo Streams 甚至不需要响应用户交互,它们还可以用于从后端异步广播页面更新。

你知道吗?它真的有效,你不需要做任何特殊操作。举个简单的例子,在你的模型中添加一个广播命令:

# app/models/comment.rb
class Comment < ApplicationRecord
  ...
  after_create_commit { broadcast_prepend_to "comments" }
end
Enter fullscreen mode Exit fullscreen mode

…并据此构建索引模板,新创建的评论将自动添加到索引页面的评论列表中:

<%# app/views/comments/index.html.erb %>
<%= turbo_stream_from "comments" %>
<div id="comments">
  <%= render @comments %>
</div>
Enter fullscreen mode Exit fullscreen mode

这也太酷了吧……?

关于 Turbo 响应中的 JavaScript 标签的说明

如果您希望在 Turbo 响应中返回 JavaScript 标签,请确保使用Turbo 7.0.0-beta8或更高版本。此次更新修复了一个导致 Turbo 响应中 JavaScript 标签无法解析的错误。

注意与 Rails UJS 的冲突

如果您之前使用非 GET 方法渲染链接,或者使用带有remote: true属性的“AJAX 化”链接,则需要注意,这些方法在启用 Turbo 的上下文中可能无法正常工作。这些功能由Rails UJS处理,可能与旧版 Rails/UJS 中的 Turbo 不兼容。从 Rails 7 开始,或者如果您仍在使用 jQuery UJS,则从jQuery UJS 1.2.3 版本开始,Rails UJS 和 Turbo应该可以和平共存

为了安全起见,非 GET 链接应该使用内联表单进行转换button_to,远程链接应该重构为由 Turbo 处理的普通链接。

其他 UJS 功能,例如禁用按钮或确认对话框,将继续正常工作。

概括

总而言之,即使你的旧版 JavaScript 代码无法立即启用 Turbo Drive(Turbolinks),Turbo 也完全可用。这真是个好消息!Turbo 使我们能够逐步重写(并有效地移除大部分)旧的手写 JavaScript 代码。我们可以为新建和更新的页面带来现代化的、高度响应式的行为,而无需事先重构所有那些陈旧的 JavaScript 代码。

一旦 JavaScript 的数量大幅减少,我们就可以处理剩余的部分,并全局启用 Turbo Drive,从而进一步加快网页浏览速度。

总的来说,我们认为这开启了前端开发的新纪元,对此我们感到非常兴奋!💛

想阅读更多类似文章吗?在推特上关注我们。

文章来源:https://dev.to/nejremeslnici/using-hotwire-turbo-in-rails-with-legacy-javascript-17g1