如何在 Rails 6 中使用 Importmaps 设置 ActionCable?(MRI 和 Jruby)

1 ruby ruby-on-rails jruby actioncable import-maps

Actioncable - 概述。

\n

我正在使用 jruby 9.2.16(因此 ruby​​ 2.5.7)和 Rails 6.1.6.1。

\n

我不确定 Actioncable 是否仅在开发中或仅在没有 ssl (wss) 的情况下可以与简单客户端一起使用:

\n
var ws = new WebSocket(\'ws://0.0.0.0:3000/channels\');\nws.onmessage = function(e){ console.log(e.data); }\n
Run Code Online (Sandbox Code Playgroud)\n

但至少我没有让它在生产中使用 wss 运行“从通道流式传输”,因为它在本地工作(在终端中启动“redis-cli”,然后“监视”)。

\n

所以我尝试实现可操作的客户端脚本,因此浪费了 8 天的时间。

\n
    \n
  1. 首先,我对没有任何完整的描述感到困惑。许多人发布特定的解决方案,但这就像赌博:也许你很幸运。

    \n
  2. \n
  3. 其次,文件的命名方式似乎很通用,即使它们仅与可操作相关(文件夹“javascript”或“application.js”)\n不将它们称为“actioncable_files”会产生误导。 ' 和 \'actioncable_app.js\' 和 voil\xc3\xa0 由于多个同名文件而出现问题。

    \n
  4. \n
  5. 下一个问题是,只需更改很多内容,因为文件的有序结构被忽略。Javascript 不再位于 asset 中,但为什么?\n它们可能位于 asset/javascripts/actioncable/ 中?\n因此,manifest.js 必须更改,甚至必须在 application.rb 中添加属性(attr_accessor :importmap)。您在论坛几天后发现的东西。

    \n
  6. \n
  7. 但更令人困惑的是: importmap 是一个 gem,它需要一些目录,并且必须以某种方式安装(rake app:update:bin、rails importmap:install),有人写道,gems 的顺序是相关的,但你不能只卸载Actioncable gem,因为 Rails 依赖于它。可以使用优先级排列来组织依赖关系。

    \n
  8. \n
  9. importmaps 在 Firefox 中不起作用,因此您还需要垫片

    \n
  10. \n
\n

但最终,importmap 所做的一切对我来说都像是重新发明轮子:它加载 javascript 文件。有些事情可以轻松手动完成。\n还可以使用简单的 JavaScript 导入模块。那么浏览器最终可以使用的 javascript 文件应该是什么呢?

\n
    \n
  1. 现在 importmap 从字符串“@rails/actioncable”创建另一个字符串“actioncable.esm.js”,在我的例子中(经过 8 天工作 15 小时使其工作后,仍然没有自动找到。
  2. \n
\n

我找不到该文件,或生成它的任何描述,或者它只是一个链接,或者必须在某个地方进行编译,但在我看来,导入映射似乎完全多余,只会使事情变得非常复杂。我不明白编写一个字符串“xyz”有什么好处,该字符串以一种费力的方式转换为另一个字符串“xyz_2”,也可能找不到。而如果有变量,可以直接使用action_cable_meta_tag相同的思路来加载。

\n

从技术上讲,Actioncable 只做了 Faye 之前做过的事情。那么,为什么我们需要所谓的“现代”方式,我认为这只是重新发明轮子呢?

\n

因此,我想创建一个关于如何以简单的方式安装 actioncable 的描述 - 无需不必要的工具且清晰。

\n

但首先我需要让它自己工作。因此问题是:由于以下原因我该怎么办:

\n
var ws = new WebSocket(\'ws://0.0.0.0:3000/channels\');\nws.onmessage = function(e){ console.log(e.data); }\n
Run Code Online (Sandbox Code Playgroud)\n

谢谢大家的任何想法!

\n

Ale*_*lex 5

首先,来自importmap-rails

注意:为了使用Action Cable、Action Text 和 Active Storage 等 Rails 框架中的 JavaScript,您必须运行Rails 7.0+。这是与这些库的 ESM 兼容版本一起发布的第一个版本。 https://github.com/rails/importmap-rails#installation

已接受的挑战。我现在将使用核磁共振成像(稍后我将尝试使用您的版本,看看是否出现任何奇怪的情况)。


行动电缆

$ rails _6.1.6.1_ new cable --skip-javascript
$ cd cable

# https://github.com/rails/importmap-rails#installation

$ bin/bundle add importmap-rails
$ bin/rails importmap:install

# ActionCable guide seems rather crusty. Until this section:
# https://guides.rubyonrails.org/v6.1/action_cable_overview.html#connect-consumer
# A generator for the client side js is mentioned in the code comment.

$ bin/rails g channel chat

# Oops, generated server side rb as well.
# This should really be at the start of the guide.
Run Code Online (Sandbox Code Playgroud)
# app/channels/chat_channel.rb

class ChatChannel < ApplicationCable::Channel
  def subscribed
    # NOTE: just keep it simple
    stream_from "some_channel"
  end
end
Run Code Online (Sandbox Code Playgroud)

该目录app/javascript看起来很通用,因为它确实如此。这适用于所有Javascript内容,由shakapackerjsbundling-railsimportmap-rails等使用。我在这里描述了一下: https ://stackoverflow.com/a/73174481/207090

// app/javascript/channels/chat_channel.js

import consumer from "./consumer"

consumer.subscriptions.create("ChatChannel", {
  connected() {
    // NOTE: We have to check if our set up is online first,
    //       before chatting and authenticating or anything else.
    console.log("ChatChannel connected")
  },

  disconnected() {},
  received(data) {}
});
Run Code Online (Sandbox Code Playgroud)

要广播消息,请在应用程序中的某个位置调用它[原文如此]:

ActionCable.server.broadcast("some_channel", "some message")
Run Code Online (Sandbox Code Playgroud)

好吧,无论如何我们必须制作一个控制器:

ActionCable.server.broadcast("some_channel", "some message")
Run Code Online (Sandbox Code Playgroud)

此外,还必须在某处导入频道才能加载到页面上。javascript_importmap_tags在布局中仅导入应用程序
https ://github.com/rails/importmap-rails#usage

$ bin/rails g scaffold Message content
$ bin/rails db:migrate
$ open http://localhost:3000/messages
$ bin/rails s
Run Code Online (Sandbox Code Playgroud)

在application.js中导入频道是有意义的。无法导入,./channels/index因为它有require. 我们必须使用节点才能使其工作,或者执行其他操作来导入所有通道。手动方式是最简单的:

<script type="module">import "application"</script>
<!--                          ^            -->
<!-- this imports the pinned `application` -->
Run Code Online (Sandbox Code Playgroud)

浏览器控制台显示缺少@rails/actioncable. 还没人告诉我要安装它。使用pin命令添加它:
https://github.com/rails/importmap-rails#using-npm-packages-via-javascript-cdns

// app/javascript/channels/index.js
// NOTE: it works a little differently with importmaps that I haven't mentioned yet.
//       skip this index file for now, and import channels in application.js

// app/javascript/application.js
import "./channels/chat_channel"
Run Code Online (Sandbox Code Playgroud)

刷新浏览器:

ChatChannel connected                                          chat_channel:5
Run Code Online (Sandbox Code Playgroud)

我们在页面上得到了 javascript。让我们让它广播:

$ bin/importmap pin @rails/actioncable
Run Code Online (Sandbox Code Playgroud)
ChatChannel connected                                          chat_channel:5
Run Code Online (Sandbox Code Playgroud)

我们知道我们是有联系的。该表单正在提交,MessagesController#create无需刷新我们正在广播到“some_channel”的位置。剩下要做的就是在页面上输出数据: https:
//guides.rubyonrails.org/v6.1/action_cable_overview.html#client-server-interactions-subscriptions

# app/controllers/messages_controller.rb

# POST /messages
def create
  @message = Message.create(message_params)
  ActionCable.server.broadcast("some_channel", @message.content)
end
Run Code Online (Sandbox Code Playgroud)

行动电缆完成。现在让我们修复importmaps


导入地图

有些事情我之前没有提到,理解起来非常重要。

一切正常,但是,仅在开发中,我在这里解释了原因:
https ://stackoverflow.com/a/73136675/207090

URL 和相对或绝对路径不会被映射,更重要的是会绕过资产管道sprockets。要实际使用importmapsapp/javascript/channels ,必须映射(也称为固定)中的所有文件,然后在导入时仅通过固定名称引用。

# config/importmap.rb

# NOTE: luckily there is a command to help with bulk pins
pin_all_from "app/javascript/channels", under: "channels"

pin "application", preload: true
pin "@rails/actioncable", to: "https://ga.jspm.io/npm:@rails/actioncable@7.0.3-1/app/assets/javascripts/actioncable.esm.js"
# NOTE: the big reveal -> follow me ->------------------------------------------------------------------^^^^^^^^^^^^^^^^^^

# NOTE: this only works in rails 7+
# pin "@rails/actioncable", to: "actioncable.esm.js"
# `actioncable.esm.js` is in the asset pipeline so to speak and can be found here:
# https://github.com/rails/rails/tree/v7.0.3.1/actioncable/app/assets/javascripts
Run Code Online (Sandbox Code Playgroud)

有关pin和的一些信息pin_all_from
/sf/answers/5099899381/

您可以在浏览器或终端中看到创建的导入映射:

$ bin/importmap json
{
  "imports": {
     "application":           "/assets/application-3ac17ae8a9bbfcdc9571d7ffac88746f5a76b18c149fdaf02fa7ed721b3e7c49.js",
     "@rails/actioncable":    "https://ga.jspm.io/npm:@rails/actioncable@7.0.3-1/app/assets/javascripts/actioncable.esm.js",
     "channels":              "/assets/channels/index-78e712d4a980790be34a2e859a2bd9a1121f9f3b508bd3f7de89889ff75828a0.js",
     "channels/chat_channel": "/assets/channels/chat_channel-0a2f983da2629a4d7edef5b7f05a494670df3f99ec6a22a2e2fee91a5d1c1d05.js",
     "channels/consumer":     "/assets/channels/consumer-b0ce945e7ae055dba9cceb062a47080dd9c7794a600762c19d38dbde3ba8ff0d.js"
  }#    ^                        ^
}  #    |                        |
   #  names you use             urls browser uses
   #    |   to import            ^   to actually get it
   #    |                        |
   #    `---> importmaped to ----'
Run Code Online (Sandbox Code Playgroud)

对于importmaps信息(不是importmap-rails gem): https:
//github.com/WICG/import-maps

导入映射不导入任何内容,它们映射nameurl. 如果您使用, ,使名称看起来像url,则没有任何内容可映射。/name./name../namehttp://js.cdn/name

<!-- app/views/messages/index.html.erb -->

<div id="chat"></div> <!-- output for broadcasted messages -->

<!-- since I have no rails ujs, for my purposes: bushcraft { remote: true } -->
<!--                                                 v                      -->
<%= form_with model: Message.new, html: { onsubmit: "remote(event, this)" } do |f| %>
  <%= f.text_field :content %>
  <%= f.submit %>
<% end %>

<script type="text/javascript">
  // you can skip this, I assume you have `rails_ujs` installed or `turbo`.
  // { remote: true } or { local: false } is all you need on the form.
  function remote(e, form) {
    e.preventDefault();
    fetch(form.action, {method: form.method, body: new FormData(form)})
    form["message[content]"].value = ""
  }
</script>
Run Code Online (Sandbox Code Playgroud)

您不想在 js 文件中使用带有绝对路径的第二种形式,因为摘要哈希会在文件更新时发生更改以使浏览器缓存无效(这是由sprockets处理的)。

转换所有导入:

// app/javascript/channels/chat_channel.js

// update received() function
received(data) {
  document.querySelector("#chat")
    .insertAdjacentHTML("beforeend", `<p>${data}</p>`)
}
Run Code Online (Sandbox Code Playgroud)

匹配固定的名称:

# config/importmap.rb

# NOTE: luckily there is a command to help with bulk pins
pin_all_from "app/javascript/channels", under: "channels"

pin "application", preload: true
pin "@rails/actioncable", to: "https://ga.jspm.io/npm:@rails/actioncable@7.0.3-1/app/assets/javascripts/actioncable.esm.js"
# NOTE: the big reveal -> follow me ->------------------------------------------------------------------^^^^^^^^^^^^^^^^^^

# NOTE: this only works in rails 7+
# pin "@rails/actioncable", to: "actioncable.esm.js"
# `actioncable.esm.js` is in the asset pipeline so to speak and can be found here:
# https://github.com/rails/rails/tree/v7.0.3.1/actioncable/app/assets/javascripts
Run Code Online (Sandbox Code Playgroud)

朱比

jruby上的设置相同。我刚刚安装了它并更新了我的Gemfile

$ bin/importmap json
{
  "imports": {
     "application":           "/assets/application-3ac17ae8a9bbfcdc9571d7ffac88746f5a76b18c149fdaf02fa7ed721b3e7c49.js",
     "@rails/actioncable":    "https://ga.jspm.io/npm:@rails/actioncable@7.0.3-1/app/assets/javascripts/actioncable.esm.js",
     "channels":              "/assets/channels/index-78e712d4a980790be34a2e859a2bd9a1121f9f3b508bd3f7de89889ff75828a0.js",
     "channels/chat_channel": "/assets/channels/chat_channel-0a2f983da2629a4d7edef5b7f05a494670df3f99ec6a22a2e2fee91a5d1c1d05.js",
     "channels/consumer":     "/assets/channels/consumer-b0ce945e7ae055dba9cceb062a47080dd9c7794a600762c19d38dbde3ba8ff0d.js"
  }#    ^                        ^
}  #    |                        |
   #  names you use             urls browser uses
   #    |   to import            ^   to actually get it
   #    |                        |
   #    `---> importmaped to ----'
Run Code Online (Sandbox Code Playgroud)

启动服务器时出现第一个错误:

import "channels/chat_channel"

// stays unchanged and is now the same as

import "/assets/channels/chat_channel-0a2f983da2629a4d7edef5b7f05a494670df3f99ec6a22a2e2fee91a5d1c1d05.js"

// because we have an importmap for "channels/chat_channel"
Run Code Online (Sandbox Code Playgroud)

importmap=方法在这里定义: https:
//github.com/rails/importmap-rails/blob/v0.7.6/lib/importmap/engine.rb#L4

Rails::Application.send(:attr_accessor, :importmap)
Run Code Online (Sandbox Code Playgroud)

jruby中,它以这种方式定义私有方法:

import consumer from "./consumer"
import "./channels/chat_channel"
Run Code Online (Sandbox Code Playgroud)

修复方法是覆盖应用程序中的定义,或将这些方法公开:

import consumer from "channels/consumer"
import "channels/chat_channel"

// import "channels" // is mapped to `channels/index`
// TODO: want to auto import channels in index file?
//       just get all the pins named *_channel and import them,
//       like stumulus-loading does for controllers:
//       https://github.com/hotwired/stimulus-rails/blob/v1.1.0/app/assets/javascripts/stimulus-loading.js#L8
Run Code Online (Sandbox Code Playgroud)

就是这样。没有其他问题。无论如何,您应该预料到会遇到一些挫折,因为您使用的是jruby,它远远落后于mri。Ruby 2.5 将于 2021 年 4 月 5 日停产。您不能指望最新的 gem 能够与旧的 Ruby 版本兼容。