Rails 7/StimulusJS 相对导入:适用于开发,但不适用于生产

Pla*_*Ton 5 ruby-on-rails asset-pipeline stimulusjs

几年后我再次使用 Rails(最后一次使用 Rails 4)。我有多个刺激控制器引用一个名为metric_defaults.js. 该文件仅包含一组平面定义,例如:

Chart.defaults.elements.line.tension = 0.25;
Chart.defaults.elements.line.borderWidth = 5;
Run Code Online (Sandbox Code Playgroud)

在 Rails 7 开发环境中,此导入可以很好地与import '../metric_defaults.js'每个刺激控制器配合使用,但在生产中我得到:

Failed to load resource: the server responded with a status of 404 (Not Found) (metric_defaults.js)
Run Code Online (Sandbox Code Playgroud)

我花了一天的时间试图找出这个问题,但所有努力都失败了。一些花絮:

  • 我正在使用标准的 Rails 7 配置(导入映射、链轮)
  • 我已经确认生产服务器正在正确预编译资产。这包括 metric_defaults.js,但是它的名称中有一个指纹(metric_defaults-9032be9e....js),而 404 似乎正在尝试访问未指纹的文件?
  • 生产服务器是一个 Heroku 实例
  • metric_defaults.js 文件的路径是 app/javascript/metric_defaults.js
  • 刺激控制器位于 app/javascript/controllers/ 中
  • 如前所述,在开发中运行良好
  • 我不知所措

任何想法表示赞赏

Ale*_*lex 3

设置:

# config/importmap.rb
pin "application"
pin "plugin"
Run Code Online (Sandbox Code Playgroud)
// app/javascript/application.js
import "./plugin";
Run Code Online (Sandbox Code Playgroud)

查看生成的导入映射:

$ bin/importmap json
{
  "imports": {
    "application": "/assets/application-6aad68dfc16d361773f71cfe7fe74ae0ace4eea0b74067bc717475bbbbf4e580.js",
    "plugin":      "/assets/plugin-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
  }#  ^              ^
}  # imports        urls
   # for you        for browser
Run Code Online (Sandbox Code Playgroud)

这很简单:

import "plugin";
// will match "plugin" from import-maps
"plugin": "/assets/plugin-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
// and turn it into
import "/assets/plugin-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js";
// browser sends request to this url ^
Run Code Online (Sandbox Code Playgroud)

但:

import "plugin";
// will match "plugin" from import-maps
"plugin": "/assets/plugin-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js"
// and turn it into
import "/assets/plugin-04024382391bb910584145d8113cf35ef376b55d125bb4516cebeb14ce788597.js";
// browser sends request to this url ^
Run Code Online (Sandbox Code Playgroud)

很确定这是我第一次关闭config.assets.quiet

import "./plugin";
// is relative to `application.js`, because that's where the import is.
// application.js is imported correctly with `import "application"` in the layout
"application": "/assets/application-6aad68dfc16d361773f71cfe7fe74ae0ace4eea0b74067bc717475bbbbf4e580.js"
//                      ^^^^^^^^^^^
// so "./plugin" is relative to this, which resolves to "/assets/plugin"

import "/assets/plugin"; // doesn't match
               "plugin"  // the import-map

import "/assets/plugin";
//      ^
// browser sends request in development and production
Run Code Online (Sandbox Code Playgroud)

在开发中/assets路由到可以处理已消化和未消化资产的链轮,并且工作正常:

Started GET "/assets/application-6aad68dfc16d361773f71cfe7fe74ae0ace4eea0b74067bc717475bbbbf4e580.js" for 127.0.0.1 at 2023-04-27 00:28:21 -0400
Started GET "/assets/es-module-shims.js-32db422c5db541b7129a2ce936aed905edc2cd481748f8d67ffe84e28313158a.map" for 127.0.0.1 at 2023-04-27 00:28:21 -0400
Started GET "/assets/plugin" for 127.0.0.1 at 2023-04-27 00:28:21 -0400
#                    ^
# NOTE: see how this one didn't get mapped to anything, it is just a plain url.
Run Code Online (Sandbox Code Playgroud)

在生产环境中,Web 服务器会代替该工作,并且只有预编译的资产,/assets/plugin会出现 404 错误。

>> Rails.application.routes.routes.detect {|i| i.ast.to_s =~ /assets/ }.app.app.class
=> Sprockets::Environment
#      ^ same thing  v                     undigested path  v
>> Rails.application.assets.call(Rack::MockRequest.env_for("plugin")).last
=> #<Sprockets::Asset:fe718 "file:///home/alex/code/stackoverflow/app/javascript/plugin.js?type=application/javascript&id=187d193631f6880345ca4c2a2ac5d3a7c06ec09a64d4fbbd2cc1eed3a614997e">
Run Code Online (Sandbox Code Playgroud)

修复#1

停止使用相对导入。

修复#2

制作一个与相对导入相匹配的导入映射:

# config/importmap.rb

pin "application"
pin "/assets/plugin", to: "plugin"
#    ^ look familiar?
Run Code Online (Sandbox Code Playgroud)
>> Rails.application.routes.routes.detect {|i| i.ast.to_s =~ /assets/ }
=> nil
Run Code Online (Sandbox Code Playgroud)
# config/importmap.rb

pin "application"
pin "/assets/plugin", to: "plugin"
#    ^ look familiar?
Run Code Online (Sandbox Code Playgroud)

如果你想走这条路,我会借用 @cesoid 的辅助方法,它会帮助你开始:

import "./plugin";      // this will be
import "/assets/plugin" // resolved to this
       "/assets/plugin" // and will match the import-map
Run Code Online (Sandbox Code Playgroud)