Class: Ruact::ClientManifest
- Inherits:
-
Object
- Object
- Ruact::ClientManifest
- 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
-
.from_hash(data) ⇒ Object
Build from an already-parsed Hash (useful in tests).
-
.load(path) ⇒ Object
Load from a file path (JSON).
Instance Method Summary collapse
-
#include?(name) ⇒ Boolean
Returns true if
nameis a top-level key in the manifest data. -
#reference_for(name, controller_path: nil) ⇒ Object
Resolve a component name (e.g. “LikeButton”) → ClientReference.
-
#resolve(module_id, _export_name) ⇒ Object
Used by Flight::Serializer to produce I rows.
Class Method Details
.from_hash(data) ⇒ Object
Build from an already-parsed Hash (useful in tests). The @reference_cache ivar is initialized eagerly so the freeze + first-lookup path works even when data is empty (otherwise reference_for would raise FrozenError trying to memoize on a frozen instance).
83 84 85 86 87 88 |
# File 'lib/ruact/client_manifest.rb', line 83 def self.from_hash(data) manifest = new manifest.instance_variable_set(:@data, data) manifest.instance_variable_set(:@reference_cache, {}) 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.
70 71 72 73 74 75 76 |
# File 'lib/ruact/client_manifest.rb', line 70 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.
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 Ruact::ManifestError when the resolved name is not found. The error message includes a Damerau-Levenshtein closest-match suggestion (Story 7.4) when a manifest entry within distance 2 exists, or a file-path hint suggesting where to add the missing component otherwise. When controller_path is given the closest-match scan biases toward co-located keys so a typo inside posts/show.html.erb surfaces the posts/_like_button suggestion before the shared LikeButton entry.
54 55 56 57 58 59 60 61 62 63 |
# File 'lib/ruact/client_manifest.rb', line 54 def reference_for(name, controller_path: nil) @reference_cache ||= {} key = resolve_key(name, controller_path) @reference_cache[key] ||= begin entry = entries_by_name[key] raise ManifestError, (name, controller_path) unless entry Flight::ClientReference.new(module_id: entry["id"], export_name: entry["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 |