如何使用 importmap 和 Sprockets 在 Rails 7 中通过 JS 中的 URL 引用资源?

yba*_*art 8 javascript ruby ruby-on-rails rails-sprockets import-maps

在应用程序中,我需要从 JS 文件实例化音频文件(我正在使用 AudioContext API),或多或少如下所示:

playAudio(url) {
  this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
  let data = await fetch(url).then(response => response.arrayBuffer());
  let buffer = await this.audioContext.decodeAudioData(data)
  const source = this.audioContext.createBufferSource()
  source.buffer = buffer
  source.connect(this.audioContext.destination)
  source.start()
}
Run Code Online (Sandbox Code Playgroud)

此 JS 文件是在使用 importmap 和 Sprockets 的新 Rails 7 应用程序中加载的 Stimulus 控制器。

在开发环境中,JS 可以猜测路径,因为 Sprockets 将以规范名称(例如/assets/audio/file.wav)来服务资产。然而,在生产中,在资源预编译期间,Sprockets 在文件名后添加一个哈希值,并且只能使用/assets/audio/file-f11ef113f11ef113f113.wav.

该文件名无法进行硬编码,因为它取决于预编译(从技术上讲,我可能可以使用哈希对路径进行硬编码,因为文件不会经常更改,但我不想对此哈希进行任何假设)。

除公共文件夹中的其他资产外,Sprockets 在预编译期间生成的清单中引用了该文件。使用Rails.application.assets_manifest.files我可以访问清单数据并安全地进行映射。

这是我为此编写的助手:

  def audio_assets_json
    audio_assets = Rails.application.assets_manifest.files.select do |_key, file|
      file['logical_path'].start_with?('audio/')
    end

    JSON.pretty_generate(
      audio_assets.to_h { |_k, f| [f['logical_path'], asset_url(f['logical_path'])] }
    )
  end
Run Code Online (Sandbox Code Playgroud)

但我需要从 JS 文件访问这些数据,并且清单的文件名中也有一个哈希值,所以我的 JS 无法简单地加载它。

我当前的解决方案是将其包含在我的应用程序布局中,效果很好:

    <script>
      window.assets = <%= audio_assets_json %>
      window.asset_url = function(path) {
        let result = assets[path]
        return result ? result : `/assets/${path}`
      }
    </script>
Run Code Online (Sandbox Code Playgroud)

此解决方案的问题在于,哈希值被写入应用程序服务器的每个 HTML 响应中,效率不高。此外,助手在运行时调用,这也是低效的:这是在运行时动态生成的,而这应该在部署构建期间静态完成。

.js.erb我最初的想法是在预编译时由 Sprockets 生成的文件中生成列表。所以我controllers/application.jscontrollers/application.js.erb这样的方式重命名并调用助手:

<% environment.context_class.instance_eval { include ApplicationHelper } %>
window.assets = <%= audio_assets_json %>
Run Code Online (Sandbox Code Playgroud)

JS 是由 Sprockets 正确生成的,但不知何故importmap无法看到它,并且 JS 控制台显示以下错误:

Unable to resolve specifier 'controllers/application' from http://localhost:3000/assets/controllers/index-2db729dddcc5b979110e98de4b6720f83f91a123172e87281d5a58410fc43806.js
Run Code Online (Sandbox Code Playgroud)

我尝试添加这一行config/initializers/assets.rb

<% environment.context_class.instance_eval { include ApplicationHelper } %>
window.assets = <%= audio_assets_json %>
Run Code Online (Sandbox Code Playgroud)

我尝试添加这一行assets/manifest.js

Unable to resolve specifier 'controllers/application' from http://localhost:3000/assets/controllers/index-2db729dddcc5b979110e98de4b6720f83f91a123172e87281d5a58410fc43806.js
Run Code Online (Sandbox Code Playgroud)

但这些都没有帮助。

所以我的问题是:如何使用 importmap 和 Sprockets 静态引用 JS 中的资产 URL?