如何将自定义的JS文件添加到新的Rails 7项目中

32
我创建了一个新的Rails 7项目rails new my_project,但是在Rails中包含我的自定义JS文件时遇到了问题。
我的"javascript/application.js"文件。
import "@hotwired/turbo-rails"
import "controllers"

import "chartkick"
import "Chart.bundle"
import "custom/uni_toggle"

我的自定义JS文件:“javascript/custom/uni_toggle.js”

function uniToggleShow() {
    document.querySelectorAll(".uni-toggle").forEach(e => e.classList.remove("hidden"))
}

function uniToggleHide() {
    console.log("uni toggle hide")
    document.querySelectorAll(".uni-toggle").forEach(e => e.classList.add("hidden"))
}

window.uniToggleShow = uniToggleShow
window.uniToggleHide = uniToggleHide

我在我的布局中使用了<%= javascript_importmap_tags %>
以及我的"confing/importmap.rb"文件。
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"
4个回答

67
1. Quickstart            - quick things you should know
2. `pin_all_from`        - a few details
3. `pin`                   ...
4. Run in a console      - when you need to figure stuff out
5. Relative imports      - don't do it, unless you want to
6. Examples              - to make it extra clear

如果你没有使用importmap-rails,真的,你不应该有任何问题。添加一个文件,然后import "./path/to/file"。如果你使用jsbundling-rails,确保运行bin/dev来编译你的JavaScript。
如果你使用importmap-rails,就没有编译,每个文件都必须在开发和生产环境中单独提供,并且每个import都必须映射到浏览器获取的URL。 pinpin_all_from是构建importmap的Rails方式。通过资产URL将导入映射到本地文件。所以请记住,import "something"可以映射到URL/assets/file-123.js,这可以映射到文件app/some_asset_path/file.js或者在生产环境中是public/assets/file-123.js
<script type="importmap" data-turbo-track="reload">{
  "imports": {
    "application":                  "/assets/application-da9b182f12cdd2de0b86de67fc7fde8d1887a7d9ffbde46937f8cd8553cb748d.js",
    "@hotwired/turbo-rails":        "/assets/turbo.min-49f8a244b039107fa6d058adce740847d31bdf3832c043b860ebcda099c0688c.js",
    "@hotwired/stimulus":           "/assets/stimulus-a1299f07b3a1d1083084767c6e16a178a910487c81874b80623f7f2e48f99a86.js",
    "@hotwired/stimulus-loading":   "/assets/stimulus-loading-6024ee603e0509bba59098881b54a52936debca30ff797835b5ec6a4ef77ba37.js",
    "controllers/application":      "/assets/controllers/application-44e5edd38372876617b8ba873a82d48737d4c089e5180f706bdea0bb7b6370be.js",
    "controllers/hello_controller": "/assets/controllers/hello_controller-29468750494634340c5c12678fe2cdc3bee371e74ac4e9de625cdb7a89faf11b.js",
    "controllers":                  "/assets/controllers/index-e70bed6fafbd4e4aae72f8c6fce4381d19507272ff2ff0febb3f775447accb4b.js",
  }#    ^                             ^
   #    |                             |
   #  names you use to import        urls browser uses to get it
   #    |                             ^ 
   #    |                             |
   #    `------>  mapped to  ---------'
}</script>

一旦你有了一个importmap,你就需要import你需要的东西。Importmap并不加载任何东西,它只是一个配置。

快速入门

假设我们已经添加了一个“插件”目录:

app/
└── javascript/
   ├── application.js   # <= imports go here and other js files
   └── plugin/
      ├── app.js
      └── index.js
config/
└── importmap.rb        # <= pins go here

将单个文件固定在顶部:
# config/importmap.rb
pin "plugin/app"
pin "plugin/index"

# app/javascript/application.js
import "plugin/app"     # which maps to a url which maps to a file
import "plugin/index"

或者将插件目录及其子目录中的所有文件固定在屏幕上:
# config/importmap.rb
pin_all_from "app/javascript/plugin", under: "plugin"

# app/javascript/application.js
import "plugin/app"
import "plugin"

不要使用相对导入,例如import "./plugin/app",在开发中可能有效,但在生产中会出现问题。
查看bin/importmap json的输出,了解可以导入的内容,并验证importmap.rb配置。

不要在开发中预编译,它将从public/assets提供预编译的资源,这些资源在您进行更改时不会更新。
运行bin/rails assets:clobber以删除预编译的资源。

如果出现问题,app/javascript目录必须位于:
Rails.application.config.assets.paths
并且app/assets/config/manifest.js中的//= link_tree ../../javascript .js


将文件固定在某个位置并不会使它们加载。它们必须在application.js中导入:
// app/javascript/application.js
import "plugin"

或者,如果你想拆分你的捆绑包,你可以在你的布局中使用一个单独的模块标签:
<%= javascript_import_module_tag "plugin" %>

或模板:
<% content_for :head do %>
  <%= javascript_import_module_tag "plugin" %>
<% end %>

# add this to the end of the <head> tag:
# <%= yield :head %>

除了 application.js 之外,您还可以添加另一个入口点,比如您添加了 app/javascript/admin.js。您可以使用所有的引脚来导入它。
# this doesn't `import` application.js anymore
<%= javascript_importmap_tags "admin" %>

因为默认情况下,application的pin选项设置为preload: true,即使您使用admin覆盖了application的入口点,它仍会发出加载application.js文件的请求。预加载和导入是两个不同的概念,一个不会导致另一个。删除preload选项以避免不必要的请求。

pin_all_from(dir, under: nil, to: nil, preload: false)

将目录及其子目录中的所有文件都固定。

https://github.com/rails/importmap-rails/blob/v1.1.2/lib/importmap/map.rb#L33

def pin_all_from(dir, under: nil, to: nil, preload: false)
  clear_cache
  @directories[dir] = MappedDir.new(dir: dir, under: under, path: to, preload: preload)
end

dir - 相对于Rails.root或绝对路径的路径。

选项:

:under - 可选[1]的前缀。如果有index.js文件,则必填。

:to - 可选[1]的资源路径。如果:under选项被省略,则回退到back。此路径相对于Rails.application.config.assets.paths

:preload - 如果设置为true,则添加一个modulepreload链接:

<link rel="modulepreload" href="/assets/turbo-5605bff731621f9ca32b71f5270be7faa9ccb0c7c810187880b97e74175d85e2.js">

注意:需要使用:under:to中的一个。
插件目录中的所有文件固定住:
pin_all_from "app/javascript/plugin", under: "plugin"

# NOTE: `index.js` file gets a special treatment, instead
#       of pinning `plugin/index` it is just `plugin`.
{
  "imports": {
    "plugin/app": "/assets/plugin/app-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js",
    "plugin": "/assets/plugin/index-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
  }
}

这是它们如何相互配合的:
(如果某些东西不起作用,就采取你的选择并按照箭头的方向走,特别是path_to_asset部分,你可以在控制台中尝试一下,见下文)
   "plugin/app": "/assets/plugin/app-04024382391bb...4145d8113cf788597.js"
#   ^      ^      ^
#   |      |      |  
# :under   |      `-path_to_asset("plugin/app.js")
#          |                       ^      ^
#          |                       |      |
#          |..       (:to||:under)-'      |
#  "#{dir}/app.js"                        |
#          '''''`-------------------------'             

:to选项在这里可能不明显。如果更改了:under选项,它将导致path_to_asset无法找到app.js

例如,:under选项可以是任何你想要的,但:to选项必须是资产管道Sprockets可以找到的路径(参见Rails.application.config.assets.paths),并且还要进行预编译(参见app/assets/config/manifest.js)。

pin_all_from "app/javascript/plugin", under: "@plug", to: "plugin"

# Outputs these pins
#
#   "@plug/app": "/assets/plugin/app-04024382391b1...16beb14ce788597.js"
#   "@plug": "/assets/plugin/index-04024382391bb91...4ebeb14ce788597.js"
#
# and can be used like this
#
#   import "@plug";
#   import "@plug/app";

指定绝对路径将绕过资源管道。
pin_all_from("app/javascript/plugin", under: "plugin", to: "/plugin")

#   "plugin/app": "/plugin/app.js"
#   "plugin": "/plugin/index.js"
#
# NOTE: It is up to you to set up `/plugin/*` route and serve these files.

pin(name, to: nil, preload: false)

固定一个单个文件。

https://github.com/rails/importmap-rails/blob/v1.1.2/lib/importmap/map.rb#L28

def pin(name, to: nil, preload: false)
  clear_cache
  @packages[name] = MappedFile.new(name: name, path: to || "#{name}.js", preload: preload)
end

name - 按钮的名称。

选项:

:to - 可选的资源路径。如果未设置,则回退到 {name}.js。此路径相对于 Rails.application.config.assets.paths

:preload - 如果设置为 true,则添加一个 modulepreload 链接。


当固定一个本地文件时,要指定相对于app/javascript目录(或vendor或任何其他资源目录)的name
pin "plugin/app"
pin "plugin/index"

{
  "imports": {
    "plugin/app": "/assets/plugin/app-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js",
    "plugin/index": "/assets/plugin/index-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
  }
}

这是它们如何配合的方式:
   "plugin/app": "/assets/plugin/app-04024382391bb...16cebeb14ce788597.js"
#   ^             ^
#   |             |  
#  name           `-path_to_asset("plugin/app.js")
#                                  ^
#                                  |
#              (:to||"#{name}.js")-'

如果你想要改变针脚的名称,:to选项是必需的,以便给path_to_asset一个有效的文件位置。
例如,要获得与pin_all_from中的针脚相同的index.js文件的针脚:
pin "plugin", to: "plugin/index"

{
  "imports": {
    "plugin": "/assets/plugin/index-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
  }
} 

在控制台中运行

你可以在控制台中尝试使用Importmap,这样可以更快地调试和学习哪些功能有效,哪些功能无效:

>> helper.path_to_asset("plugin/app.js")
=> "/assets/plugin/app-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"

>> map = Importmap::Map.new
>> map.pin_all_from("app/javascript/plugin", under: "plugin")
>> puts map.to_json(resolver: helper)
{
  "imports": {
    "plugin/app": "/assets/plugin/app-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js",
    "plugin": "/assets/plugin/index-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
  }
}

>> map.pin("application")
>> puts map.to_json(resolver: helper)
{
  "imports": {
    "application": "/assets/application-8cab2d9024ef6f21fd55792af40001fd4ee1b72b8b7e14743452fab1348b4f5a.js"
  }
}

# Importmap from config/importmap.rb
>> Rails.application.importmap

相对/绝对导入

如果你进行正确的映射,相对/绝对导入是可行的:

# config/importmap.rb
pin "/assets/plugin/app", to: "plugin/app.js"

// app/javascript/application.js
import "./plugin/app"

application.js 被映射到摘要为 /assets/application-123.js,因为 ./plugin/app 是相对于 /assets/application-123.js 的,所以它应该被正确解析为 /assets/plugin/app,我们使用我们的 pin 创建了一个 importmap 来实现这个。

"/assets/plugin/app": "/assets/plugin/app-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js",

这个也应该可以正常工作:
// app/javascript/plugin/index.js
import "./app"

然而,在importmap-rails中,虽然import-maps支持所有相对和绝对导入,但这似乎并不是其预期的使用情况。

示例

这应该涵盖了几乎所有内容:

.
├── app/
│   └── javascript/
│       ├── admin.js
│       ├── application.js
│       ├── extra/
│       │   └── nested/
│       │       └── directory/
│       │           └── special.js
│       └── plugin/
│           ├── app.js
│           └── index.js
└── vendor/
    └── javascript/
        ├── downloaded.js
        └── package/
            └── vendored.js

从运行bin/importmap json的输出中得到的结果是:
# this is the only time when both `to` and `under` options can be omitted
# you don't really want to do this, at least not for `app/javascript`
pin_all_from "app/javascript"
pin_all_from "vendor/javascript"

"admin":                          "/assets/admin-761ee3050e9046942e5918c64dbfee795eeade86bf3fec34ec126c0d43c931b0.js",
"application":                    "/assets/application-d0d262731ff4f756b418662f3149e17b608d2aab7898bb983abeb669cc73bf2e.js",
"extra/nested/directory/special": "/assets/extra/nested/directory/special-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js",
"plugin/app":                     "/assets/plugin/app-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js",
"plugin":                         "/assets/plugin/index-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js",
"downloaded":                     "/assets/downloaded-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js",
"package/vendored":               "/assets/package/vendored-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"

请注意区别:
pin_all_from "app/javascript/extra", under: "extra"    # `to: "extra"` is implied
"extra/nested/directory/special": "/assets/extra/nested/directory/special-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
 ^

pin_all_from "app/javascript/extra", to: "extra"
"nested/directory/special": "/assets/extra/nested/directory/special-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
 ^

pin_all_from "app/javascript/extra", under: "@name", to: "extra"
"@name/nested/directory/special": "/assets/extra/nested/directory/special-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
 ^

请注意这个模式:
pin_all_from "app/javascript"
pin_all_from "app/javascript/extra",                  under: "extra"
pin_all_from "app/javascript/extra/nested",           under: "extra/nested"
pin_all_from "app/javascript/extra/nested/directory", under: "extra/nested/directory"

pin_all_from "app/javascript/extra",                  to: "extra"
pin_all_from "app/javascript/extra/nested",           to: "extra/nested"
pin_all_from "app/javascript/extra/nested/directory", to: "extra/nested/directory"

pin_all_from "app/javascript/extra",                  under: "@name", to: "extra"
pin_all_from "app/javascript/extra/nested",           under: "@name", to: "extra/nested"
pin_all_from "app/javascript/extra/nested/directory", under: "@name", to: "extra/nested/directory"

同样的事情也适用于供应商。
pin_all_from "vendor/javascript/package", under: "package"
# etc

单个文件很容易:
pin "admin"
pin "application"
pin "extra/nested/directory/special"
pin "@extra/special", to: "extra/nested/directory/special"

pin "downloaded"
pin "renamed", to: "downloaded"

pin_all_from失败时:
# if you ever tried this, it doesn't work:
# pin_all_from "app/javascript", under: "@app", to: ""
# but it can be done:
app_js = Rails.root.join("app/javascript")
app_js.glob("**/*.js").each do |path|
  name = path.relative_path_from(app_js).to_s.chomp(".js")
  pin "@app/#{name}", to: name
end
# useful for things like `app/components`

7
这篇帖子值得所有的点赞。谢谢。 - Leo Policastro
如果你想让相对导入起作用,请参考最后一个代码片段以了解如何在默认情况下进行操作。但是,没有什么能阻止你让 import "pallete"import "@fractal/pallete" 起作用:pin_all_from "app/javascript/fractal_viewer", to: "fractal_viewer"pin_all_from "app/javascript/fractal_viewer", under: "@fractal", to: "fractal_viewer"。但请注意,更改导入方式比调试一些边缘情况问题容易得多。在这种情况下,最好坚持使用 esbuild。 - Alex
自从这个答案发布以来,有没有任何变化使得使用相对导入更容易?或者是否有一些有效的等价物可以让我在所有导入中不硬编码站点大型隔离特性的目录名称(例如,避免导入'fractal_viewer/palette.js')?[编辑:删除了我的无意义的冗长陈述。] - cesoid
1
@Alex 谢谢。我最终采用了这个方法:def pin_all_relative(dir_name) ; pin_all_from "app/javascript/#{dir_name}", under: "#{Rails.application.config.assets.prefix}/#{dir_name}", to: dir_name ; end 然后每个“组件”调用一次它。解释一下:我的网站有超过200个自己的模块,分为几层深的目录,即使最深层的模块有时也会在没有引用其父目录的情况下导入进来。总共大约有600个导入语句,我需要更改而无法使用简单的查找/替换,并且我可能会失去一些灵活性和可重用性。 - cesoid
2
这个答案就像是一本关于importmaps的遗失手册。 - undefined
显示剩余2条评论

25

我在我的Rails 7应用程序中添加自定义JS文件时也遇到了困难。 我甚至跟随了DHH视频 --> https://www.youtube.com/watch?v=PtxZvFnL2i0,但仍然面临困难。 下面的步骤对我有用:

  1. 转到config/importmap.rb并添加以下内容:

    pin_all_from "app/javascript/custom", under: "custom"

  2. 转到app/javascript/application.js文件并添加以下内容:

    import "custom/main"

  3. 'app/javascript'目录中添加'custom'文件夹。

  4. 'app/javascript/custom'目录中添加您的自定义js文件'main.js'。

  5. 在终端中运行:

    rails assets:precompile

  6. 启动Rails服务器。 就这样


4
这种方式可行,但在编辑自定义JS文件后运行“rails assets:precompile”并不是正确的行为。 - depassion
2
@depassion 我认为那个视频现在有点过时了。你不必再预编译了。 - Kulbir Saini
我发现了这个问题并按照这里的所有步骤进行了操作,包括JavaScript文件,但对我有效的方法是将自定义JavaScript放在视图文件底部。那么,有什么区别或“正确”的方法吗? - Cleber Reizen
第二点应该是 import "./custom/main"。 - Karthick Nagarajan
2
不应该这样做。像 "./custom/main" 这样的导入最终会直接指向 assets 文件夹。它们在生产环境中会出现问题。 - johncip
在开发过程中,最好不要预编译资产并且永远不再预编译它们。然后使用"bin/dev"而不是"rails server"来启动服务器。这样,当资产发生变化时,它们会自动更新。 - undefined

22

在看完 DHH 的视频后,我找到了最后一块拼图。

为了使我的自定义 JS 代码工作,我只需将此行添加到 "confing/importmap.rb" 中即可。

pin_all_from "app/javascript/custom", under: "custom"

那么,这样做,我们将我们的JS文件添加到“importmap”中,但是如果我有多个布局并且它们都有不同的JS文件呢? - Rizlan Nawfal Tamima

1
如果你想使用importmap,就按照之前人们的回答去做。
但是如果你在importmap中添加文件,这意味着在每个布局中都会加载该文件。但是如果你只想添加单个JS文件,我的建议是使用一个简单的`

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接