Class: Ruact::ClientManifest

Inherits:
Object
  • Object
show all
Defined in:
lib/ruact/client_manifest.rb

Overview

Reads the react-client-manifest.json emitted by the Vite plugin and resolves component names to Flight ClientReferences.

Manifest format (one entry per “use client” export):

{
  "LikeButton": {
    "id":     "/assets/LikeButton-abc123.js",
    "chunks": ["/assets/LikeButton-abc123.js"],
    "name":   "LikeButton"
  },
  "posts/_like_button": {
    "id":     "/assets/posts/_like_button-abc123.js",
    "chunks": ["/assets/posts/_like_button-abc123.js"],
    "name":   "default"
  }
}

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.from_hash(data) ⇒ Object

Build from an already-parsed Hash (useful in tests).



76
77
78
79
80
# File 'lib/ruact/client_manifest.rb', line 76

def self.from_hash(data)
  manifest = new
  manifest.instance_variable_set(:@data, data)
  manifest
end

.load(path) ⇒ Object

Load from a file path (JSON). Pre-warms the reference cache and freezes the manifest so it cannot be mutated at runtime (AC#5). Pre-warming is required because Ruby’s freeze is shallow: instance variable assignment on a frozen object raises FrozenError, so @reference_cache must already be set before freeze.



67
68
69
70
71
72
73
# File 'lib/ruact/client_manifest.rb', line 67

def self.load(path)
  raw      = File.read(path)
  data     = JSON.parse(raw)
  manifest = from_hash(data)
  data.each_key { |name| manifest.reference_for(name) }
  manifest.freeze
end

Instance Method Details

#include?(name) ⇒ Boolean

Returns true if name is a top-level key in the manifest data. Used by the dual-path resolver to check co-located key existence before fallback.

Returns:

  • (Boolean)


34
35
36
# File 'lib/ruact/client_manifest.rb', line 34

def include?(name)
  entries_by_name.key?(name)
end

#reference_for(name, controller_path: nil) ⇒ Object

Resolve a component name (e.g. “LikeButton”) → ClientReference.

When controller_path is provided (e.g. “posts”), the resolver first looks for a co-located key (“posts/_like_button”). If found, it returns that reference; otherwise it falls back to the shared PascalCase key.

Returns the same object for repeated calls with the same resolved key (needed for dedup by object_id in Flight::Serializer). Raises if the resolved name is not found in the manifest.



47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/ruact/client_manifest.rb', line 47

def reference_for(name, controller_path: nil)
  @reference_cache ||= {}
  key = resolve_key(name, controller_path)
  @reference_cache[key] ||= begin
    entry = entries_by_name[key]
    unless entry
      raise ManifestError,
            "Component #{name.inspect} not found in manifest — " \
            "Did you run the Vite build? Run 'npm run build' or start the Vite dev server."
    end

    Flight::ClientReference.new(module_id: entry["id"], export_name: name)
  end
end

#resolve(module_id, _export_name) ⇒ Object

Used by Flight::Serializer to produce I rows. Returns the metadata array the client expects: [id, name, chunks]



25
26
27
28
29
30
# File 'lib/ruact/client_manifest.rb', line 25

def resolve(module_id, _export_name)
  entry = by_module_id(module_id)
  raise "ClientManifest: no entry for module_id=#{module_id.inspect}" unless entry

  [entry["id"], entry["name"], entry["chunks"]]
end