Class: Ruact::Railtie
- Inherits:
-
Rails::Railtie
- Object
- Rails::Railtie
- Ruact::Railtie
- Defined in:
- lib/ruact/railtie.rb
Class Method Summary collapse
-
.check_manifest!(manifest_path) ⇒ Object
Checks whether the manifest exists and either warns (dev) or raises (prod).
-
.check_vite! ⇒ Object
Checks whether the Vite dev server is accessible and warns if not (AC#4).
-
.detect_streaming_mode! ⇒ Object
Detects the web server at boot, stores the streaming mode, and logs the result (AC#1–3).
-
.force_routes_loaded! ⇒ Boolean
Idempotently force the route table to load.
-
.write_server_functions_snapshot! ⇒ Array<Hash>?
Story 9.9 — writes the route-driven (v2) server-functions JSON snapshot to the REAL bridge (
tmp/cache/ruact/server-functions.json) on everyconfig.to_prepare.
Class Method Details
.check_manifest!(manifest_path) ⇒ Object
Checks whether the manifest exists and either warns (dev) or raises (prod). Extracted as a class method for direct testability without a full Rails app.
172 173 174 175 176 177 178 179 180 181 |
# File 'lib/ruact/railtie.rb', line 172 def self.check_manifest!(manifest_path) if Rails.env.production? raise ManifestError, "react-client-manifest.json not found — run vite build before deploying" else Rails.logger.warn "[ruact] react-client-manifest.json not found at " \ "#{manifest_path} — RSC rendering will be unavailable. " \ "Run 'npm run build' or start the Vite dev server." end end |
.check_vite! ⇒ Object
Checks whether the Vite dev server is accessible and warns if not (AC#4). Extracted as a class method for direct testability without a full Rails app.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/ruact/railtie.rb', line 101 def self.check_vite! require "socket" require "uri" uri = URI.parse(Ruact.config.vite_dev_server) host = uri.host || "localhost" port = uri.port || 5173 # `connect_timeout` so a blackholed/remote configured host can't stall dev # boot from `after_initialize` until the OS TCP timeout (matches # ViewHelper#vite_dev_running?). TCPSocket.new(host, port, connect_timeout: 1).close rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ETIMEDOUT, SocketError # Broad rescue (incl. SocketError) because the host is now configurable: a # misconfigured/unresolvable `vite_dev_server` must downgrade to this dev # warning, never crash boot from `after_initialize`. `host`/`port` are # assigned before the connect attempt, so they are always in scope here. Rails.logger.warn "[ruact] Vite dev server not detected at #{host}:#{port} " \ "— run npm run dev for HMR" end |
.detect_streaming_mode! ⇒ Object
Detects the web server at boot, stores the streaming mode, and logs the result (AC#1–3). Detection is constant-based (zero I/O): Puma → enabled, Unicorn/Passenger → buffered, unknown → buffered (safe mode).
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/ruact/railtie.rb', line 80 def self.detect_streaming_mode! mode, label = if defined?(::Puma::Server) [:enabled, "Puma detected"] elsif defined?(::Falcon::Server) [:enabled, "Falcon detected"] elsif defined?(::Unicorn) [:buffered, "Unicorn detected"] elsif defined?(::PhusionPassenger) [:buffered, "Passenger detected"] else [:buffered, "server unknown — defaulting to safe mode"] end Ruact.streaming_mode = mode verb = mode == :enabled ? "enabled" : "buffered" Rails.logger.info "[ruact] streaming: #{verb} (#{label})" mode end |
.force_routes_loaded! ⇒ Boolean
Idempotently force the route table to load. Returns true when the table is
safely loaded (so the caller may read it), false when the force re-entered
the boot finisher's in-progress draw (Rails < 8 raises FrozenError
there) — in which case the caller must NOT write, leaving the table to the
routes initializer + the later after_initialize write.
160 161 162 163 164 165 166 167 168 |
# File 'lib/ruact/railtie.rb', line 160 def self.force_routes_loaded! reloader = Rails.application.routes_reloader return true unless reloader.respond_to?(:execute_unless_loaded) reloader.execute_unless_loaded true rescue FrozenError false end |
.write_server_functions_snapshot! ⇒ Array<Hash>?
Story 9.9 — writes the route-driven (v2) server-functions JSON snapshot
to the REAL bridge (tmp/cache/ruact/server-functions.json) on every
config.to_prepare. The Vite plugin renders this JSON into
app/javascript/.ruact/server-functions.ts. The write is short-circuited
when the entries are unchanged (Story 8.0a pitfall #1 — dev mode fires
to_prepare per request; a naive rewrite would burn IOPS and churn the
Vite plugin's chokidar watcher).
Cold-boot ordering (folded in from the Story 9.8 host workaround): on the
very first boot to_prepare can run BEFORE routes.rb is drawn, leaving
the route set empty so the codegen would emit zero functions. Force the
route table to load first via routes_reloader.execute_unless_loaded so
RouteSource/QuerySource see every route.
The force-load is guarded: when to_prepare fires from INSIDE the
boot finisher (Rails draws routes during initialize!, and on Rails < 8
re-entering execute_unless_loaded there raises FrozenError against the
in-progress route set), we SKIP THE WRITE ENTIRELY rather than publish a
snapshot from a mid-draw/incomplete route set (which Vite could observe as
a transient empty/stale bridge — Codex R1). The authoritative boot write
is the after_initialize call, which always sees the fully-drawn table;
the dev-reload to_prepare then refreshes from a settled table.
145 146 147 148 149 150 151 |
# File 'lib/ruact/railtie.rb', line 145 def self.write_server_functions_snapshot! return nil unless force_routes_loaded! Ruact::ServerFunctions.write_v2_snapshot!( route_set: Rails.application.routes, root: Rails.root ) end |