# 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, **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