You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
153 lines
5.1 KiB
153 lines
5.1 KiB
# frozen_string_literal: true |
|
|
|
module ViteRuby::ManifestIntegrityExtension |
|
def path_and_integrity_for(name, **) |
|
entry = lookup!(name, **) |
|
|
|
{ path: entry.fetch('file'), integrity: entry.fetch('integrity', nil) } |
|
end |
|
|
|
def load_manifest |
|
# Invalidate the name lookup cache when reloading manifest |
|
@name_lookup_cache = nil unless dev_server_running? |
|
|
|
super |
|
end |
|
|
|
def load_name_lookup_cache |
|
Oj.load(config.build_output_dir.join('.vite/manifest-lookup.json').read) |
|
end |
|
|
|
# Upstream's `virtual` type is a hack, re-implement it with efficient exact name lookup |
|
def resolve_virtual_entry(name) |
|
return name if dev_server_running? |
|
|
|
@name_lookup_cache ||= load_name_lookup_cache |
|
|
|
@name_lookup_cache.fetch(name) |
|
end |
|
|
|
# Find a manifest entry by the *final* file name |
|
def integrity_hash_for_file(file_name) |
|
@integrity_cache ||= {} |
|
@integrity_cache[file_name] ||= begin |
|
entry = manifest.find { |_key, entry| entry['file'] == file_name } |
|
|
|
entry[1].fetch('integrity', nil) if entry |
|
end |
|
end |
|
|
|
def resolve_entries_with_integrity(*names, **options) |
|
entries = names.map { |name| lookup!(name, **options) } |
|
script_paths = entries.map do |entry| |
|
{ |
|
file: entry.fetch('file'), |
|
# TODO: Secure this so we require the integrity hash outside of dev |
|
integrity: entry['integrity'], |
|
} |
|
end |
|
|
|
imports = dev_server_running? ? [] : entries.flat_map { |entry| entry['imports'] }.compact |
|
|
|
{ |
|
scripts: script_paths, |
|
imports: imports.filter_map { |entry| { file: entry.fetch('file'), integrity: entry.fetch('integrity') } }.uniq, |
|
stylesheets: dev_server_running? ? [] : (entries + imports).flat_map { |entry| entry['css'] }.compact.uniq, |
|
} |
|
end |
|
|
|
# We need to override this method to not include the manifest, as in our case it is too large and will cause a JSON max nesting error rather than raising the expected exception |
|
def missing_entry_error(name, **) |
|
raise ViteRuby::MissingEntrypointError.new( |
|
file_name: resolve_entry_name(name, **), |
|
last_build: builder.last_build_metadata, |
|
manifest: '', |
|
config: config |
|
) |
|
end |
|
end |
|
|
|
ViteRuby::Manifest.prepend ViteRuby::ManifestIntegrityExtension |
|
|
|
module ViteRails::TagHelpers::IntegrityExtension |
|
def vite_javascript_tag(*names, |
|
type: 'module', |
|
asset_type: :javascript, |
|
skip_preload_tags: false, |
|
skip_style_tags: false, |
|
crossorigin: 'anonymous', |
|
media: 'screen', |
|
**options) |
|
entries = vite_manifest.resolve_entries_with_integrity(*names, type: asset_type) |
|
|
|
''.html_safe.tap do |tags| |
|
entries.fetch(:scripts).each do |script| |
|
tags << javascript_include_tag( |
|
script[:file], |
|
integrity: script[:integrity], |
|
crossorigin: crossorigin, |
|
type: type, |
|
extname: false, |
|
**options |
|
) |
|
end |
|
|
|
unless skip_preload_tags |
|
entries.fetch(:imports).each do |import| |
|
tags << vite_preload_tag(import[:file], integrity: import[:integrity], crossorigin: crossorigin, **options) |
|
end |
|
end |
|
|
|
options[:extname] = false if Rails::VERSION::MAJOR >= 7 |
|
|
|
unless skip_style_tags |
|
entries.fetch(:stylesheets).each do |stylesheet| |
|
# This is for stylesheets imported from Javascript. The entry for the JS entrypoint only contains the final CSS file name, so we need to look it up in the manifest |
|
tags << stylesheet_link_tag( |
|
stylesheet, |
|
integrity: vite_manifest.integrity_hash_for_file(stylesheet), |
|
media: media, |
|
crossorigin: crossorigin, |
|
**options |
|
) |
|
end |
|
end |
|
end |
|
end |
|
|
|
def vite_stylesheet_tag(*names, type: :stylesheet, **options) |
|
''.html_safe.tap do |tags| |
|
names.each do |name| |
|
entry = vite_manifest.path_and_integrity_for(name, type:) |
|
|
|
options[:extname] = false if Rails::VERSION::MAJOR >= 7 |
|
|
|
tags << stylesheet_link_tag(entry[:path], integrity: entry[:integrity], **options) |
|
end |
|
end |
|
end |
|
|
|
def vite_preload_file_tag(name, |
|
asset_type: :javascript, |
|
crossorigin: 'anonymous', **options) |
|
''.html_safe.tap do |tags| |
|
entries = vite_manifest.resolve_entries_with_integrity(name, type: asset_type) |
|
|
|
entries.fetch(:scripts).each do |script| |
|
tags << vite_preload_tag(script[:file], integrity: script[:integrity], crossorigin: crossorigin, **options) |
|
end |
|
end |
|
rescue ViteRuby::MissingEntrypointError |
|
# Ignore this error, it is not critical if the file is not preloaded |
|
end |
|
|
|
def vite_polyfills_tag(crossorigin: 'anonymous', **) |
|
return if ViteRuby.instance.dev_server_running? |
|
|
|
entry = vite_manifest.path_and_integrity_for('polyfills', type: :virtual) |
|
|
|
javascript_include_tag(entry[:path], type: 'module', integrity: entry[:integrity], crossorigin: crossorigin, **) |
|
end |
|
end |
|
|
|
ViteRails::TagHelpers.prepend ViteRails::TagHelpers::IntegrityExtension
|
|
|