Как ссылаться на активы по URL-адресу в JS в rails 7 с помощью importmap и Sprockets?
В приложении мне нужно создавать аудиофайлы из 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()
}
Этот файл JS представляет собой контроллер Stimulus, загруженный в новое приложение Rails 7, которое использует importmap и Sprockets.
В среде разработки 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
Но мне нужно получить доступ к этим данным из JS-файла, а поскольку манифест также имеет хэш в имени файла, мой JS не может просто загрузить его.
Мое текущее решение состоит в том, чтобы включить его в макет моего приложения, и это отлично работает:
<script>
window.assets = <%= audio_assets_json %>
window.asset_url = function(path) {
let result = assets[path]
return result ? result : `/assets/${path}`
}
</script>
Проблема с этим решением заключается в том, что хэш записывается в каждый HTML-ответ от сервера приложений, что неэффективно. Кроме того, помощник вызывается во время выполнения, что также неэффективно: он генерируется динамически во время выполнения, тогда как это должно выполняться статически во время сборки развертывания.
Моя первоначальная идея состояла в том, чтобы создать список в
.js.erb
файл, сгенерированный Sprockets во время прекомпиляции. Поэтому я переименовал
controllers/application.js
с участием
controllers/application.js.erb
и вызвал помощник таким образом:
<% environment.context_class.instance_eval { include ApplicationHelper } %>
window.assets = <%= audio_assets_json %>
JS был правильно сгенерирован Sprockets, но каким-то образом
importmap
не мог его увидеть, и консоль JS показывает следующую ошибку:
Unable to resolve specifier 'controllers/application' from http://localhost:3000/assets/controllers/index-2db729dddcc5b979110e98de4b6720f83f91a123172e87281d5a58410fc43806.js
Я попытался добавить эту строку в
config/initializers/assets.rb
:
Sprockets.register_mime_type 'application/javascript', extensions: ['.js.erb']
Я попытался добавить эту строку в
assets/manifest.js
:
//= link_tree ../../javascript .js.erb
Но ничего из этого не помогло.
Итак, мой вопрос: как я могу ссылаться на URL-адрес активов из JS, используя importmap и Sprockets статически?