inertia-rage 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +1 -1
- data/lib/inertia/configuration.rb +50 -0
- data/lib/inertia/frontend.rb +79 -3
- data/lib/inertia/inertia.rb +8 -0
- data/lib/inertia/rage_extension.rb +14 -0
- data/lib/inertia/ssr/client.rb +56 -0
- data/lib/inertia/ssr/server.rb +29 -0
- data/lib/inertia/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9d633e20c984eefe3e5c928a6328e17964fb879dc285a81bcda592d9a57e9ea5
|
|
4
|
+
data.tar.gz: a8079309590a94cdb78cb91fe6b380aadb992b0ccd8816a7ff79cb4bcd48c640
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e3f2d8f819e89054d3c655eef9784b02f840f12fcf39a7bcadd952b4b5a01e965806d22fdd1baa20714cc8fc021fee44292cd3f2c6fe34a74d5e63e2ecccca58
|
|
7
|
+
data.tar.gz: dc09e729d8f5859faeef881184eb1fa5ec5b2863b2fcd45849ce02f75dcfd53fcf1d726cfaf92772984bd54dd712fbf87c169d1f46affea4404d7da41a1491ae
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
|
@@ -213,7 +213,7 @@ See the [API documentation](https://inertia-api.rage-rb.dev/Inertia/Configuratio
|
|
|
213
213
|
## Learn More
|
|
214
214
|
|
|
215
215
|
- [Props API Reference](https://inertia-api.rage-rb.dev/Inertia)
|
|
216
|
-
- [Controller API Reference](https://inertia-api.rage-rb.dev/Inertia
|
|
216
|
+
- [Controller API Reference](https://inertia-api.rage-rb.dev/RageController/Inertia.html)
|
|
217
217
|
- [Configuration API Reference](https://inertia-api.rage-rb.dev/Inertia/Configuration.html)
|
|
218
218
|
|
|
219
219
|
## Contributing
|
|
@@ -90,6 +90,13 @@ module Inertia
|
|
|
90
90
|
@dev_server ||= DevServer.new
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
+
# Returns the SSR configuration.
|
|
94
|
+
#
|
|
95
|
+
# @return [Ssr] the SSR configuration object
|
|
96
|
+
def ssr
|
|
97
|
+
@ssr ||= Ssr.new
|
|
98
|
+
end
|
|
99
|
+
|
|
93
100
|
##
|
|
94
101
|
# Stores configuration for the Vite development server.
|
|
95
102
|
#
|
|
@@ -106,5 +113,48 @@ module Inertia
|
|
|
106
113
|
@port = 5173
|
|
107
114
|
end
|
|
108
115
|
end
|
|
116
|
+
|
|
117
|
+
##
|
|
118
|
+
# Stores configuration for server-side rendering.
|
|
119
|
+
#
|
|
120
|
+
class Ssr
|
|
121
|
+
# @return [Boolean] whether SSR is enabled
|
|
122
|
+
attr_accessor :enabled
|
|
123
|
+
|
|
124
|
+
# @return [String] the URL of the SSR server (default: `http://localhost:13714`)
|
|
125
|
+
attr_accessor :url
|
|
126
|
+
|
|
127
|
+
# @return [Pathname, nil] the path to the SSR build output
|
|
128
|
+
attr_accessor :build_path
|
|
129
|
+
|
|
130
|
+
# @private
|
|
131
|
+
def initialize
|
|
132
|
+
@url = "http://localhost:13714"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Sets the path where SSR bundle is located.
|
|
136
|
+
#
|
|
137
|
+
# When set, this overrides the default `ssr` directory inside {Frontend.dist}.
|
|
138
|
+
# The path should be relative to the application root.
|
|
139
|
+
#
|
|
140
|
+
# @param path [String] path relative to the application root
|
|
141
|
+
# @raise [ArgumentError] if path resolves to the public directory
|
|
142
|
+
def build_path=(path)
|
|
143
|
+
candidate_path = Rage.root.join(path)
|
|
144
|
+
public_path = Rage.root.join("public")
|
|
145
|
+
|
|
146
|
+
if candidate_path.to_s.start_with?(public_path.to_s)
|
|
147
|
+
raise ArgumentError, "ssr.build_path cannot be inside public/"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
@build_path = candidate_path
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @private
|
|
154
|
+
def local?
|
|
155
|
+
normalized = URI(@url).hostname.downcase
|
|
156
|
+
%w[localhost localhost. 0.0.0.0 ::1].include?(normalized) || normalized.start_with?("127.")
|
|
157
|
+
end
|
|
158
|
+
end
|
|
109
159
|
end
|
|
110
160
|
end
|
data/lib/inertia/frontend.rb
CHANGED
|
@@ -37,6 +37,26 @@ module Inertia
|
|
|
37
37
|
@dist ||= Inertia.config.build_path || root.join("dist")
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Returns the directory containing built SSR assets.
|
|
41
|
+
#
|
|
42
|
+
# Uses {Configuration::Ssr#build_path} if set, otherwise defaults to
|
|
43
|
+
# `dist/ssr` inside the frontend root.
|
|
44
|
+
#
|
|
45
|
+
# @return [Pathname] path to the build output directory
|
|
46
|
+
# @raise [RuntimeError] if no Vite config file is found
|
|
47
|
+
def ssr_dist
|
|
48
|
+
@ssr_dist ||= begin
|
|
49
|
+
path = Inertia.config.ssr.build_path || dist.join("ssr")
|
|
50
|
+
public_path = Rage.root.join("public")
|
|
51
|
+
|
|
52
|
+
if path.to_s.start_with?(public_path.to_s)
|
|
53
|
+
raise ArgumentError, "SSR build path cannot be inside public/; configure it explicitly via config.ssr.build_path"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
path
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
40
60
|
# Returns a version identifier for the frontend assets.
|
|
41
61
|
#
|
|
42
62
|
# Computes an MD5 hash of the Vite manifest or index.html to detect
|
|
@@ -76,6 +96,29 @@ module Inertia
|
|
|
76
96
|
@package_runner
|
|
77
97
|
end
|
|
78
98
|
|
|
99
|
+
# Returns the JavaScript runtime command.
|
|
100
|
+
#
|
|
101
|
+
# Detects the runtime by checking for lock files and returns
|
|
102
|
+
# the appropriate command to execute scripts.
|
|
103
|
+
#
|
|
104
|
+
# @return [String] runtime command (e.g., "node", "bun", "deno run")
|
|
105
|
+
# @raise [RuntimeError] if no supported runtime is detected
|
|
106
|
+
def runtime
|
|
107
|
+
@runtime ||= if root.join("package-lock.json").exist?
|
|
108
|
+
"node"
|
|
109
|
+
elsif root.join("pnpm-lock.yaml").exist?
|
|
110
|
+
"node"
|
|
111
|
+
elsif root.join("bun.lockb").exist? || root.join("bun.lock").exist?
|
|
112
|
+
"bun"
|
|
113
|
+
elsif root.join("yarn.lock").exist?
|
|
114
|
+
"yarn node"
|
|
115
|
+
elsif root.join("deno.lock").exist?
|
|
116
|
+
"deno run --allow-net --allow-env"
|
|
117
|
+
else
|
|
118
|
+
raise "No supported JavaScript runtime detected"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
79
122
|
# Renders the HTML layout with the Inertia page object embedded.
|
|
80
123
|
#
|
|
81
124
|
# In development, it fetches the layout from the Vite dev server and
|
|
@@ -122,7 +165,7 @@ module Inertia
|
|
|
122
165
|
"from \"#{dev_server_url}/#{$2}\""
|
|
123
166
|
end
|
|
124
167
|
|
|
125
|
-
|
|
168
|
+
hydrate_layout(layout, data)
|
|
126
169
|
end
|
|
127
170
|
|
|
128
171
|
# Returns the cached static layout with page data.
|
|
@@ -131,13 +174,46 @@ module Inertia
|
|
|
131
174
|
# @return [String] HTML with page data injected
|
|
132
175
|
# @raise [RuntimeError] if index.html does not exist in build path
|
|
133
176
|
def build_static_layout(data)
|
|
134
|
-
@
|
|
177
|
+
@static_layout ||= begin
|
|
135
178
|
layout = dist.join("index.html")
|
|
136
179
|
raise "Production layout not found at #{layout}. Ensure the frontend has been built" unless layout.exist?
|
|
137
180
|
layout.read
|
|
138
181
|
end
|
|
139
182
|
|
|
140
|
-
|
|
183
|
+
hydrate_layout(@static_layout, data)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def hydrate_layout(layout, data)
|
|
187
|
+
if Inertia.config.ssr.enabled
|
|
188
|
+
ssr_layout = inject_ssr_data(layout, data)
|
|
189
|
+
return ssr_layout if ssr_layout
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
inject_page_data(layout, data)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def inject_ssr_data(layout, data)
|
|
196
|
+
ssr_data = Inertia::SSR::Client.render(data)
|
|
197
|
+
|
|
198
|
+
processed_layout = layout.sub(/<div\s[^>]*\bid=(["'])app\1[^>]*>\s*<\/div>/i) { ssr_data["body"] }
|
|
199
|
+
# the app container is not empty - fallback to CSR
|
|
200
|
+
return nil if processed_layout == layout
|
|
201
|
+
|
|
202
|
+
if (head = ssr_data["head"]).any?
|
|
203
|
+
processed_layout.sub!(/<head([^>]*)>/i) do |head_tag|
|
|
204
|
+
<<~HTML
|
|
205
|
+
#{head_tag}
|
|
206
|
+
#{ssr_data["head"].join}
|
|
207
|
+
HTML
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
processed_layout
|
|
212
|
+
|
|
213
|
+
rescue => e
|
|
214
|
+
Rage.logger.error("SSR render failed", exception: e.message)
|
|
215
|
+
Rage::Errors.report(e)
|
|
216
|
+
nil
|
|
141
217
|
end
|
|
142
218
|
|
|
143
219
|
# Injects the page object JSON into the HTML body.
|
data/lib/inertia/inertia.rb
CHANGED
|
@@ -83,6 +83,14 @@ module Inertia
|
|
|
83
83
|
# @!endgroup
|
|
84
84
|
|
|
85
85
|
autoload :ViteDevServer, "inertia/vite_dev_server"
|
|
86
|
+
|
|
87
|
+
module SSR
|
|
88
|
+
autoload :Client, "inertia/ssr/client"
|
|
89
|
+
autoload :Server, "inertia/ssr/server"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
class SSRServerError < StandardError
|
|
93
|
+
end
|
|
86
94
|
end
|
|
87
95
|
|
|
88
96
|
module RageController
|
|
@@ -15,6 +15,11 @@ module Inertia
|
|
|
15
15
|
# Automatically start Vite dev server in development mode
|
|
16
16
|
config.daemons << ViteDevServer if Rage.env.development?
|
|
17
17
|
|
|
18
|
+
# Automatically start the SSR server
|
|
19
|
+
after_initialize do
|
|
20
|
+
config.daemons << SSR::Server if Inertia.config.ssr.enabled && Inertia.config.ssr.local? && !Rage.env.development?
|
|
21
|
+
end
|
|
22
|
+
|
|
18
23
|
# In production, serve prebuilt static assets via the public file server and Assets middleware
|
|
19
24
|
unless Rage.env.development?
|
|
20
25
|
config.public_file_server.enabled = true
|
|
@@ -36,6 +41,15 @@ module Inertia
|
|
|
36
41
|
puts "INFO: Building frontend"
|
|
37
42
|
puts ""
|
|
38
43
|
system("#{Frontend.package_runner} vite build", chdir: Frontend.root) || abort("ERROR: Frontend build failed")
|
|
44
|
+
|
|
45
|
+
if Inertia.config.ssr.enabled
|
|
46
|
+
if Inertia.config.ssr.local?
|
|
47
|
+
out_dir = Frontend.ssr_dist.relative_path_from(Frontend.root)
|
|
48
|
+
system("#{Frontend.package_runner} vite build --ssr --outDir #{out_dir}", chdir: Frontend.root) || abort("ERROR: SSR build failed")
|
|
49
|
+
else
|
|
50
|
+
puts "INFO: SSR server is remote - skipping SSR build"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
39
53
|
end
|
|
40
54
|
end
|
|
41
55
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
|
|
5
|
+
module Inertia
|
|
6
|
+
module SSR
|
|
7
|
+
##
|
|
8
|
+
# HTTP client for communicating with the SSR server.
|
|
9
|
+
#
|
|
10
|
+
# Sends page data to the SSR server and receives rendered HTML
|
|
11
|
+
# containing the `head` and `body` fragments.
|
|
12
|
+
#
|
|
13
|
+
class Client
|
|
14
|
+
class << self
|
|
15
|
+
# Renders the Inertia page data on the SSR server.
|
|
16
|
+
#
|
|
17
|
+
# @param data [Hash] the Inertia page object to render
|
|
18
|
+
# @return [Hash] parsed response containing "head" and "body" keys
|
|
19
|
+
def render(data)
|
|
20
|
+
response = begin
|
|
21
|
+
Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https", open_timeout: 1, read_timeout: 1) do |http|
|
|
22
|
+
http.post(uri.request_uri, data.to_json, "Content-Type" => "application/json")
|
|
23
|
+
end
|
|
24
|
+
rescue => e
|
|
25
|
+
raise Inertia::SSRServerError, e
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
unless response.code == "200"
|
|
29
|
+
raise Inertia::SSRServerError, "Request failed with status #{response.code}: #{response.body}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
JSON.parse(response.body)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# Returns the URI for the SSR endpoint.
|
|
38
|
+
#
|
|
39
|
+
# In development, uses the Vite dev server's SSR endpoint.
|
|
40
|
+
# In production, uses the configured {Configuration::Ssr#url}.
|
|
41
|
+
#
|
|
42
|
+
# @return [URI] the SSR server URI
|
|
43
|
+
def uri
|
|
44
|
+
@uri ||= if Rage.env.development?
|
|
45
|
+
dev_server_config = Inertia.config.dev_server
|
|
46
|
+
URI("http://#{dev_server_config.host}:#{dev_server_config.port}/__inertia_ssr")
|
|
47
|
+
else
|
|
48
|
+
uri = URI(Inertia.config.ssr.url)
|
|
49
|
+
uri.path = "/render" if uri.path.empty? || uri.path == "/"
|
|
50
|
+
uri
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end # class << self
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Inertia
|
|
4
|
+
module SSR
|
|
5
|
+
##
|
|
6
|
+
# Background daemon that runs the Node.js SSR server.
|
|
7
|
+
#
|
|
8
|
+
class Server < Rage::Daemon
|
|
9
|
+
def perform
|
|
10
|
+
puts "INFO: Starting SSR server"
|
|
11
|
+
|
|
12
|
+
ssr_bundle = Frontend.ssr_dist.glob("*.{js,mjs}").first
|
|
13
|
+
|
|
14
|
+
unless ssr_bundle&.exist?
|
|
15
|
+
Rage.logger.fatal("Could not start SSR server - bundle not found")
|
|
16
|
+
return Rage::Daemon::Stop
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
@pid = Process.spawn("#{Frontend.runtime} #{ssr_bundle.relative_path_from(Frontend.root)}", chdir: Frontend.root)
|
|
20
|
+
Process.wait(@pid)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def cleanup
|
|
24
|
+
Process.kill("TERM", @pid) if @pid
|
|
25
|
+
rescue Errno::ESRCH
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/inertia/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: inertia-rage
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Roman Samoilov
|
|
@@ -49,6 +49,8 @@ files:
|
|
|
49
49
|
- lib/inertia/renderer.rb
|
|
50
50
|
- lib/inertia/request_context.rb
|
|
51
51
|
- lib/inertia/rspec.rb
|
|
52
|
+
- lib/inertia/ssr/client.rb
|
|
53
|
+
- lib/inertia/ssr/server.rb
|
|
52
54
|
- lib/inertia/version.rb
|
|
53
55
|
- lib/inertia/vite_dev_server.rb
|
|
54
56
|
homepage: https://rage-rb.dev
|