class ViteRuby::Manifest

Public: Registry for accessing resources managed by Vite, using a generated manifest file which maps entrypoint names to file paths.

Example:

lookup_entrypoint('calendar', type: :javascript)
=> { "file" => "/vite/assets/calendar-1016838bab065ae1e314.js", "imports" => [] }

NOTE: Using “autoBuild”: true` in `config/vite.json` file will trigger a build on demand as needed, before performing any lookup.

Constants

FS_PREFIX

Internal: The prefix used by Vite.js to request files with an absolute path.

Public Class Methods

new(vite_ruby) click to toggle source
# File lib/vite_ruby/manifest.rb, line 13
def initialize(vite_ruby)
  @vite_ruby = vite_ruby
  @build_mutex = Mutex.new if config.auto_build
end

Public Instance Methods

path_for(name, **options) click to toggle source

Public: Returns the path for the specified Vite entrypoint file.

Raises an error if the resource could not be found in the manifest.

# File lib/vite_ruby/manifest.rb, line 21
def path_for(name, **options)
  lookup!(name, **options).fetch('file')
end
react_refresh_preamble() click to toggle source

Public: The content of the preamble needed by the React Refresh plugin.

# File lib/vite_ruby/manifest.rb, line 50
  def react_refresh_preamble
    if dev_server_running?
      <<~REACT_REFRESH
        <script type="module">
          import RefreshRuntime from '#{ prefix_asset_with_host('@react-refresh') }'
          RefreshRuntime.injectIntoGlobalHook(window)
          window.$RefreshReg$ = () => {}
          window.$RefreshSig$ = () => (type) => type
          window.__vite_plugin_react_preamble_installed__ = true
        </script>
      REACT_REFRESH
    end
  end
refresh() click to toggle source

Public: Refreshes the cached mappings by reading the updated manifest files.

# File lib/vite_ruby/manifest.rb, line 40
def refresh
  @manifest = load_manifest
end
resolve_entries(*names, **options) click to toggle source

Public: Returns scripts, imported modules, and stylesheets for the specified entrypoint files.

# File lib/vite_ruby/manifest.rb, line 27
def resolve_entries(*names, **options)
  entries = names.map { |name| lookup!(name, **options) }
  script_paths = entries.map { |entry| entry.fetch('file') }

  imports = dev_server_running? ? [] : entries.flat_map { |entry| entry['imports'] }.compact.uniq
  {
    scripts: script_paths,
    imports: imports.map { |entry| entry.fetch('file') }.uniq,
    stylesheets: dev_server_running? ? [] : (entries + imports).flat_map { |entry| entry['css'] }.compact.uniq,
  }
end
vite_client_src() click to toggle source

Public: The path from where the browser can download the Vite HMR client.

# File lib/vite_ruby/manifest.rb, line 45
def vite_client_src
  prefix_asset_with_host('@vite/client') if dev_server_running?
end

Protected Instance Methods

lookup(name, **options) click to toggle source

Internal: Computes the path for a given Vite asset using manifest.json.

Returns a relative path, or nil if the asset is not found.

Example:

manifest.lookup('calendar.js')
=> { "file" => "/vite/assets/calendar-1016838bab065ae1e122.js", "imports" => [] }
# File lib/vite_ruby/manifest.rb, line 80
def lookup(name, **options)
  @build_mutex.synchronize { builder.build } if should_build?

  find_manifest_entry resolve_entry_name(name, **options)
end
lookup!(name, **options) click to toggle source

Internal: Strict version of lookup.

Returns a relative path for the asset, or raises an error if not found.

# File lib/vite_ruby/manifest.rb, line 69
def lookup!(name, **options)
  lookup(name, **options) || missing_entry_error(name, **options)
end

Private Instance Methods

extension_for_type(entry_type) click to toggle source

Internal: Allows to receive :javascript and :stylesheet as :type in helpers.

# File lib/vite_ruby/manifest.rb, line 184
def extension_for_type(entry_type)
  case entry_type
  when :javascript then 'js'
  when :stylesheet then 'css'
  when :typescript then 'ts'
  else entry_type
  end
end
find_manifest_entry(name) click to toggle source

Internal: Finds the specified entry in the manifest.

# File lib/vite_ruby/manifest.rb, line 102
def find_manifest_entry(name)
  if dev_server_running?
    { 'file' => prefix_vite_asset(name) }
  else
    manifest[name]
  end
end
load_manifest() click to toggle source

Internal: Loads and merges the manifest files, resolving the asset paths.

# File lib/vite_ruby/manifest.rb, line 121
def load_manifest
  files = [config.manifest_path, config.assets_manifest_path].select(&:exist?)
  files.map { |path| JSON.parse(path.read) }.inject({}, &:merge).tap(&method(:resolve_references))
end
manifest() click to toggle source

Internal: The parsed data from manifest.json.

NOTE: When using build-on-demand in development and testing, the manifest is reloaded automatically before each lookup, to ensure it's always fresh.

# File lib/vite_ruby/manifest.rb, line 114
def manifest
  return refresh if config.auto_build

  @manifest ||= load_manifest
end
missing_entry_error(name, **options) click to toggle source

Internal: Raises a detailed message when an entry is missing in the manifest.

# File lib/vite_ruby/manifest.rb, line 194
def missing_entry_error(name, **options)
  raise ViteRuby::MissingEntrypointError, OpenStruct.new(
    file_name: resolve_entry_name(name, **options),
    last_build: builder.last_build_metadata,
    manifest: @manifest,
    config: config,
  )
end
prefix_asset_with_host(path) click to toggle source

Internal: Prefixes an asset with the `asset_host` for tags that do not use the framework tag helpers.

# File lib/vite_ruby/manifest.rb, line 133
def prefix_asset_with_host(path)
  File.join(config.asset_host || '/', config.public_output_dir, path)
end
prefix_vite_asset(path) click to toggle source

Internal: Scopes an asset to the output folder in public, as a path.

# File lib/vite_ruby/manifest.rb, line 127
def prefix_vite_asset(path)
  File.join("/#{ config.public_output_dir }", path)
end
resolve_absolute_entry(name) click to toggle source

Internal: Entry names in the manifest are relative to the Vite.js. During develoment, files outside the root must be requested explicitly.

# File lib/vite_ruby/manifest.rb, line 166
def resolve_absolute_entry(name)
  if dev_server_running?
    File.join(FS_PREFIX, config.root, name)
  else
    config.root.join(name).relative_path_from(config.vite_root_dir).to_s
  end
end
resolve_entry_name(name, type: nil) click to toggle source

Internal: Resolves the manifest entry name for the specified resource.

# File lib/vite_ruby/manifest.rb, line 149
def resolve_entry_name(name, type: nil)
  name = with_file_extension(name.to_s, type)

  raise ArgumentError, "Asset names can not be relative. Found: #{ name }" if name.start_with?('.') && !name.include?('legacy-polyfills')

  # Explicit path, relative to the source_code_dir.
  name.sub(%r{^~/(.+)$}) { return Regexp.last_match(1) }

  # Explicit path, relative to the project root.
  name.sub(%r{^/(.+)$}) { return resolve_absolute_entry(Regexp.last_match(1)) }

  # Sugar: Prefix with the entrypoints dir if the path is not nested.
  name.include?('/') ? name : File.join(config.entrypoints_dir, name)
end
resolve_references(manifest) click to toggle source

Internal: Resolves the paths that reference a manifest entry.

# File lib/vite_ruby/manifest.rb, line 138
def resolve_references(manifest)
  manifest.each_value do |entry|
    entry['file'] = prefix_vite_asset(entry['file'])
    %w[css assets].each do |key|
      entry[key] = entry[key].map { |path| prefix_vite_asset(path) } if entry[key]
    end
    entry['imports']&.map! { |name| manifest.fetch(name) }
  end
end
should_build?() click to toggle source

NOTE: Auto compilation is convenient when running tests, when the developer won't focus on the frontend, or when running the Vite server is not desired.

# File lib/vite_ruby/manifest.rb, line 97
def should_build?
  config.auto_build && !dev_server_running?
end
with_file_extension(name, entry_type) click to toggle source

Internal: Adds a file extension to the file name, unless it already has one.

# File lib/vite_ruby/manifest.rb, line 175
def with_file_extension(name, entry_type)
  if File.extname(name).empty? && (ext = extension_for_type(entry_type))
    "#{ name }.#{ ext }"
  else
    name
  end
end