react_on_rails 15.0.0.rc.2 → 16.0.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 +65 -36
- data/CLAUDE.md +90 -0
- data/CODING_AGENTS.md +312 -0
- data/CONTRIBUTING.md +378 -3
- data/Gemfile.lock +2 -1
- data/LICENSE.md +16 -4
- data/LICENSES/README.md +14 -0
- data/REACT-ON-RAILS-PRO-LICENSE.md +129 -0
- data/README.md +2 -2
- data/TODO.md +135 -0
- data/eslint.config.ts +2 -0
- data/lib/generators/USAGE +4 -5
- data/lib/generators/react_on_rails/base_generator.rb +263 -57
- data/lib/generators/react_on_rails/bin/dev +38 -22
- data/lib/generators/react_on_rails/dev_tests_generator.rb +1 -0
- data/lib/generators/react_on_rails/generator_helper.rb +31 -1
- data/lib/generators/react_on_rails/generator_messages.rb +138 -17
- data/lib/generators/react_on_rails/install_generator.rb +222 -20
- data/lib/generators/react_on_rails/react_no_redux_generator.rb +6 -5
- data/lib/generators/react_on_rails/react_with_redux_generator.rb +37 -13
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev +5 -0
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-prod-assets +8 -0
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static-assets +2 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -5
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/server-bundle.js +1 -8
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx +21 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css +4 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.jsx +5 -0
- data/lib/generators/react_on_rails/templates/base/base/app/views/hello_world/index.html.erb.tt +1 -1
- data/lib/generators/react_on_rails/templates/base/base/app/views/layouts/hello_world.html.erb +4 -2
- data/lib/generators/react_on_rails/templates/base/base/babel.config.js.tt +5 -2
- data/lib/generators/react_on_rails/templates/base/base/bin/dev +46 -0
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +3 -3
- data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +76 -7
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +1 -1
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +8 -8
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/production.js.tt +2 -2
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/test.js.tt +2 -2
- data/lib/generators/react_on_rails/templates/dev_tests/spec/system/hello_world_spec.rb +0 -2
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -6
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +4 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.jsx +5 -0
- data/lib/react_on_rails/configuration.rb +5 -5
- data/lib/react_on_rails/controller.rb +5 -3
- data/lib/react_on_rails/dev/file_manager.rb +78 -0
- data/lib/react_on_rails/dev/pack_generator.rb +27 -0
- data/lib/react_on_rails/dev/process_manager.rb +61 -0
- data/lib/react_on_rails/dev/server_manager.rb +330 -0
- data/lib/react_on_rails/dev.rb +20 -0
- data/lib/react_on_rails/engine.rb +6 -0
- data/lib/react_on_rails/git_utils.rb +12 -2
- data/lib/react_on_rails/helper.rb +61 -17
- data/lib/react_on_rails/packer_utils.rb +4 -18
- data/lib/react_on_rails/packs_generator.rb +134 -8
- data/lib/react_on_rails/react_component/render_options.rb +2 -2
- data/lib/react_on_rails/server_rendering_js_code.rb +0 -1
- data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +1 -0
- data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +1 -0
- data/lib/react_on_rails/utils.rb +16 -1
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_syntax_converter.rb +1 -1
- data/lib/react_on_rails.rb +1 -0
- data/lib/tasks/generate_packs.rake +20 -0
- data/react_on_rails.gemspec +1 -0
- metadata +40 -11
- data/REACT-ON-RAILS-PRO-LICENSE +0 -95
- data/lib/generators/react_on_rails/adapt_for_older_shakapacker_generator.rb +0 -41
- data/lib/generators/react_on_rails/bin/dev-static +0 -30
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static.tt +0 -9
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt +0 -5
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/registration.js.tt +0 -8
- /data/lib/generators/react_on_rails/templates/base/base/config/webpack/{webpackConfig.js.tt → generateWebpackConfigs.js.tt} +0 -0
- /data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/{HelloWorldApp.jsx → HelloWorldApp.client.jsx} +0 -0
@@ -0,0 +1,330 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "English"
|
4
|
+
require "open3"
|
5
|
+
require "rainbow"
|
6
|
+
|
7
|
+
module ReactOnRails
|
8
|
+
module Dev
|
9
|
+
class ServerManager
|
10
|
+
class << self
|
11
|
+
def start(mode = :development, procfile = nil, verbose: false)
|
12
|
+
case mode
|
13
|
+
when :production_like
|
14
|
+
run_production_like(_verbose: verbose)
|
15
|
+
when :static
|
16
|
+
procfile ||= "Procfile.dev-static-assets"
|
17
|
+
run_static_development(procfile, verbose: verbose)
|
18
|
+
when :development, :hmr
|
19
|
+
procfile ||= "Procfile.dev"
|
20
|
+
run_development(procfile, verbose: verbose)
|
21
|
+
else
|
22
|
+
raise ArgumentError, "Unknown mode: #{mode}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def kill_processes
|
27
|
+
puts "🔪 Killing all development processes..."
|
28
|
+
puts ""
|
29
|
+
|
30
|
+
killed_any = kill_running_processes || cleanup_socket_files
|
31
|
+
|
32
|
+
print_kill_summary(killed_any)
|
33
|
+
end
|
34
|
+
|
35
|
+
def development_processes
|
36
|
+
{
|
37
|
+
"rails" => "Rails server",
|
38
|
+
"node.*react[-_]on[-_]rails" => "React on Rails Node processes",
|
39
|
+
"overmind" => "Overmind process manager",
|
40
|
+
"foreman" => "Foreman process manager",
|
41
|
+
"ruby.*puma" => "Puma server",
|
42
|
+
"webpack-dev-server" => "Webpack dev server",
|
43
|
+
"bin/shakapacker-dev-server" => "Shakapacker dev server"
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def kill_running_processes
|
48
|
+
killed_any = false
|
49
|
+
|
50
|
+
development_processes.each do |pattern, description|
|
51
|
+
pids = find_process_pids(pattern)
|
52
|
+
next unless pids.any?
|
53
|
+
|
54
|
+
puts " ☠️ Killing #{description} (PIDs: #{pids.join(', ')})"
|
55
|
+
terminate_processes(pids)
|
56
|
+
killed_any = true
|
57
|
+
end
|
58
|
+
|
59
|
+
killed_any
|
60
|
+
end
|
61
|
+
|
62
|
+
def find_process_pids(pattern)
|
63
|
+
stdout, _status = Open3.capture2("pgrep", "-f", pattern, err: File::NULL)
|
64
|
+
stdout.split("\n").map(&:to_i).reject { |pid| pid == Process.pid }
|
65
|
+
rescue Errno::ENOENT
|
66
|
+
# pgrep command not found
|
67
|
+
[]
|
68
|
+
end
|
69
|
+
|
70
|
+
def terminate_processes(pids)
|
71
|
+
pids.each do |pid|
|
72
|
+
Process.kill("TERM", pid)
|
73
|
+
rescue StandardError
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def cleanup_socket_files
|
79
|
+
files = [".overmind.sock", "tmp/sockets/overmind.sock", "tmp/pids/server.pid"]
|
80
|
+
killed_any = false
|
81
|
+
|
82
|
+
files.each do |file|
|
83
|
+
next unless File.exist?(file)
|
84
|
+
|
85
|
+
puts " 🧹 Removing #{file}"
|
86
|
+
File.delete(file)
|
87
|
+
killed_any = true
|
88
|
+
rescue StandardError
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
|
92
|
+
killed_any
|
93
|
+
end
|
94
|
+
|
95
|
+
def print_kill_summary(killed_any)
|
96
|
+
if killed_any
|
97
|
+
puts ""
|
98
|
+
puts "✅ All processes terminated and sockets cleaned"
|
99
|
+
puts "💡 You can now run 'bin/dev' for a clean start"
|
100
|
+
else
|
101
|
+
puts " ℹ️ No development processes found running"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def show_help
|
106
|
+
puts help_usage
|
107
|
+
puts ""
|
108
|
+
puts help_commands
|
109
|
+
puts ""
|
110
|
+
puts help_options
|
111
|
+
puts ""
|
112
|
+
puts help_customization
|
113
|
+
puts ""
|
114
|
+
puts help_mode_details
|
115
|
+
puts ""
|
116
|
+
puts help_troubleshooting
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def help_usage
|
122
|
+
Rainbow("📋 Usage: bin/dev [command] [options]").bold
|
123
|
+
end
|
124
|
+
|
125
|
+
# rubocop:disable Metrics/AbcSize
|
126
|
+
def help_commands
|
127
|
+
<<~COMMANDS
|
128
|
+
#{Rainbow('🚀 COMMANDS:').cyan.bold}
|
129
|
+
#{Rainbow('(none) / hmr').green.bold} #{Rainbow('Start development server with HMR (default)').white}
|
130
|
+
#{Rainbow('→ Uses:').yellow} Procfile.dev
|
131
|
+
|
132
|
+
#{Rainbow('static').green.bold} #{Rainbow('Start development server with static assets (no HMR, no FOUC)').white}
|
133
|
+
#{Rainbow('→ Uses:').yellow} Procfile.dev-static-assets
|
134
|
+
|
135
|
+
#{Rainbow('production-assets').green.bold} #{Rainbow('Start with production-optimized assets (no HMR)').white}
|
136
|
+
#{Rainbow('prod').green.bold} #{Rainbow('Alias for production-assets').white}
|
137
|
+
#{Rainbow('→ Uses:').yellow} Procfile.dev-prod-assets
|
138
|
+
|
139
|
+
#{Rainbow('kill').red.bold} #{Rainbow('Kill all development processes for a clean start').white}
|
140
|
+
#{Rainbow('help').blue.bold} #{Rainbow('Show this help message').white}
|
141
|
+
COMMANDS
|
142
|
+
end
|
143
|
+
# rubocop:enable Metrics/AbcSize
|
144
|
+
|
145
|
+
def help_options
|
146
|
+
<<~OPTIONS
|
147
|
+
#{Rainbow('⚙️ OPTIONS:').cyan.bold}
|
148
|
+
#{Rainbow('--verbose, -v').green.bold} #{Rainbow('Enable verbose output for pack generation').white}
|
149
|
+
OPTIONS
|
150
|
+
end
|
151
|
+
|
152
|
+
def help_customization
|
153
|
+
<<~CUSTOMIZATION
|
154
|
+
#{Rainbow('🔧 CUSTOMIZATION:').cyan.bold}
|
155
|
+
Each mode uses a specific Procfile that you can customize for your application:
|
156
|
+
|
157
|
+
#{Rainbow('•').yellow} #{Rainbow('Procfile.dev').green.bold} - HMR development with webpack-dev-server
|
158
|
+
#{Rainbow('•').yellow} #{Rainbow('Procfile.dev-static-assets').green.bold} - Static development with webpack --watch
|
159
|
+
#{Rainbow('•').yellow} #{Rainbow('Procfile.dev-prod-assets').green.bold} - Production-optimized assets (port 3001)
|
160
|
+
|
161
|
+
#{Rainbow('Edit these files to customize the development environment for your needs.').white}
|
162
|
+
CUSTOMIZATION
|
163
|
+
end
|
164
|
+
|
165
|
+
# rubocop:disable Metrics/AbcSize
|
166
|
+
def help_mode_details
|
167
|
+
<<~MODES
|
168
|
+
#{Rainbow('🔥 HMR Development mode (default)').cyan.bold} - #{Rainbow('Procfile.dev').green}:
|
169
|
+
#{Rainbow('•').yellow} #{Rainbow('Hot Module Replacement (HMR) enabled').white}
|
170
|
+
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation before Procfile start').white}
|
171
|
+
#{Rainbow('•').yellow} #{Rainbow('Webpack dev server for fast recompilation').white}
|
172
|
+
#{Rainbow('•').yellow} #{Rainbow('Source maps for debugging').white}
|
173
|
+
#{Rainbow('•').yellow} #{Rainbow('May have Flash of Unstyled Content (FOUC)').white}
|
174
|
+
#{Rainbow('•').yellow} #{Rainbow('Fast recompilation').white}
|
175
|
+
#{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000/hello_world').cyan.underline}
|
176
|
+
|
177
|
+
#{Rainbow('📦 Static development mode').cyan.bold} - #{Rainbow('Procfile.dev-static-assets').green}:
|
178
|
+
#{Rainbow('•').yellow} #{Rainbow('No HMR (static assets with auto-recompilation)').white}
|
179
|
+
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation before Procfile start').white}
|
180
|
+
#{Rainbow('•').yellow} #{Rainbow('Webpack watch mode for auto-recompilation').white}
|
181
|
+
#{Rainbow('•').yellow} #{Rainbow('CSS extracted to separate files (no FOUC)').white}
|
182
|
+
#{Rainbow('•').yellow} #{Rainbow('Development environment (faster builds than production)').white}
|
183
|
+
#{Rainbow('•').yellow} #{Rainbow('Source maps for debugging').white}
|
184
|
+
#{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3000/hello_world').cyan.underline}
|
185
|
+
|
186
|
+
#{Rainbow('🏭 Production-assets mode').cyan.bold} - #{Rainbow('Procfile.dev-prod-assets').green}:
|
187
|
+
#{Rainbow('•').yellow} #{Rainbow('React on Rails pack generation before Procfile start').white}
|
188
|
+
#{Rainbow('•').yellow} #{Rainbow('Asset precompilation with production optimizations').white}
|
189
|
+
#{Rainbow('•').yellow} #{Rainbow('Optimized, minified bundles').white}
|
190
|
+
#{Rainbow('•').yellow} #{Rainbow('Extracted CSS files (no FOUC)').white}
|
191
|
+
#{Rainbow('•').yellow} #{Rainbow('No HMR (static assets)').white}
|
192
|
+
#{Rainbow('•').yellow} #{Rainbow('Slower recompilation').white}
|
193
|
+
#{Rainbow('•').yellow} #{Rainbow('Access at:').white} #{Rainbow('http://localhost:3001/hello_world').cyan.underline}
|
194
|
+
MODES
|
195
|
+
end
|
196
|
+
# rubocop:enable Metrics/AbcSize
|
197
|
+
|
198
|
+
def run_production_like(_verbose: false)
|
199
|
+
procfile = "Procfile.dev-prod-assets"
|
200
|
+
|
201
|
+
print_procfile_info(procfile)
|
202
|
+
print_server_info(
|
203
|
+
"🏭 Starting production-like development server...",
|
204
|
+
[
|
205
|
+
"Generating React on Rails packs",
|
206
|
+
"Precompiling assets with production optimizations",
|
207
|
+
"Running Rails server on port 3001",
|
208
|
+
"No HMR (Hot Module Replacement)",
|
209
|
+
"CSS extracted to separate files (no FOUC)"
|
210
|
+
],
|
211
|
+
3001
|
212
|
+
)
|
213
|
+
|
214
|
+
# Precompile assets in production mode (includes pack generation automatically)
|
215
|
+
puts "🔨 Precompiling assets..."
|
216
|
+
success = system "RAILS_ENV=production NODE_ENV=production bundle exec rails assets:precompile"
|
217
|
+
|
218
|
+
if success
|
219
|
+
puts "✅ Assets precompiled successfully"
|
220
|
+
ProcessManager.ensure_procfile(procfile)
|
221
|
+
ProcessManager.run_with_process_manager(procfile)
|
222
|
+
else
|
223
|
+
puts "❌ Asset precompilation failed"
|
224
|
+
exit 1
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def run_static_development(procfile, verbose: false)
|
229
|
+
print_procfile_info(procfile)
|
230
|
+
print_server_info(
|
231
|
+
"⚡ Starting development server with static assets...",
|
232
|
+
[
|
233
|
+
"Generating React on Rails packs",
|
234
|
+
"Using shakapacker --watch (no HMR)",
|
235
|
+
"CSS extracted to separate files (no FOUC)",
|
236
|
+
"Development environment (source maps, faster builds)",
|
237
|
+
"Auto-recompiles on file changes"
|
238
|
+
]
|
239
|
+
)
|
240
|
+
|
241
|
+
PackGenerator.generate(verbose: verbose)
|
242
|
+
ProcessManager.ensure_procfile(procfile)
|
243
|
+
ProcessManager.run_with_process_manager(procfile)
|
244
|
+
end
|
245
|
+
|
246
|
+
def run_development(procfile, verbose: false)
|
247
|
+
print_procfile_info(procfile)
|
248
|
+
PackGenerator.generate(verbose: verbose)
|
249
|
+
ProcessManager.ensure_procfile(procfile)
|
250
|
+
ProcessManager.run_with_process_manager(procfile)
|
251
|
+
end
|
252
|
+
|
253
|
+
def print_server_info(title, features, port = 3000)
|
254
|
+
puts title
|
255
|
+
features.each { |feature| puts " - #{feature}" }
|
256
|
+
puts ""
|
257
|
+
puts ""
|
258
|
+
puts "💡 Access at: #{Rainbow("http://localhost:#{port}/hello_world").cyan.underline}"
|
259
|
+
puts ""
|
260
|
+
end
|
261
|
+
|
262
|
+
def print_procfile_info(procfile)
|
263
|
+
port = procfile_port(procfile)
|
264
|
+
box_width = 60
|
265
|
+
|
266
|
+
puts ""
|
267
|
+
puts box_border(box_width)
|
268
|
+
puts box_empty_line(box_width)
|
269
|
+
puts format_box_line("📋 Using Procfile: #{procfile}", box_width)
|
270
|
+
puts format_box_line("🔧 Customize this file for your app's needs", box_width)
|
271
|
+
puts box_empty_line(box_width)
|
272
|
+
puts format_box_line("💡 Access at: #{Rainbow("http://localhost:#{port}/hello_world").cyan.underline}",
|
273
|
+
box_width)
|
274
|
+
puts box_empty_line(box_width)
|
275
|
+
puts box_bottom(box_width)
|
276
|
+
puts ""
|
277
|
+
end
|
278
|
+
|
279
|
+
def procfile_port(procfile)
|
280
|
+
procfile == "Procfile.dev-prod-assets" ? 3001 : 3000
|
281
|
+
end
|
282
|
+
|
283
|
+
def box_border(width)
|
284
|
+
"┌#{'─' * (width - 2)}┐"
|
285
|
+
end
|
286
|
+
|
287
|
+
def box_bottom(width)
|
288
|
+
"└#{'─' * (width - 2)}┘"
|
289
|
+
end
|
290
|
+
|
291
|
+
def box_empty_line(width)
|
292
|
+
"│#{' ' * (width - 2)}│"
|
293
|
+
end
|
294
|
+
|
295
|
+
def format_box_line(content, box_width)
|
296
|
+
line = "│ #{content}"
|
297
|
+
# Use visual length for colored text
|
298
|
+
visual_length = Rainbow.uncolor(line).length
|
299
|
+
padding = box_width - visual_length - 2
|
300
|
+
line + "#{' ' * padding}│"
|
301
|
+
end
|
302
|
+
|
303
|
+
# rubocop:disable Metrics/AbcSize
|
304
|
+
def help_troubleshooting
|
305
|
+
<<~TROUBLESHOOTING
|
306
|
+
#{Rainbow('🔧 TROUBLESHOOTING:').cyan.bold}
|
307
|
+
|
308
|
+
#{Rainbow('⚛️ React Refresh Issues:').yellow.bold}
|
309
|
+
#{Rainbow('If you see "$RefreshSig$ is not defined" errors:').white}
|
310
|
+
#{Rainbow('1.').green} #{Rainbow('Check that both babel plugin and webpack plugin are configured:').white}
|
311
|
+
#{Rainbow('•').yellow} #{Rainbow('babel.config.js: \'react-refresh/babel\' plugin (enabled when WEBPACK_SERVE=true)').white}
|
312
|
+
#{Rainbow('•').yellow} #{Rainbow('config/webpack/development.js: ReactRefreshWebpackPlugin (enabled when WEBPACK_SERVE=true)').white}
|
313
|
+
#{Rainbow('2.').green} #{Rainbow('Ensure you\'re running HMR mode:').white} #{Rainbow('bin/dev').green.bold} #{Rainbow('(not').white} #{Rainbow('bin/dev static').red}#{Rainbow(')').white}
|
314
|
+
#{Rainbow('3.').green} #{Rainbow('Try restarting the development server:').white} #{Rainbow('bin/dev kill && bin/dev').green.bold}
|
315
|
+
#{Rainbow('4.').green} #{Rainbow('Note: React Refresh only works in HMR mode, not static mode').white}
|
316
|
+
|
317
|
+
#{Rainbow('🚨 General Issues:').yellow.bold}
|
318
|
+
#{Rainbow('•').red} #{Rainbow('"Port already in use"').white} #{Rainbow('→ Run:').yellow} #{Rainbow('bin/dev kill').green.bold}
|
319
|
+
#{Rainbow('•').red} #{Rainbow('"Webpack compilation failed"').white} #{Rainbow('→ Check console for specific errors').white}
|
320
|
+
#{Rainbow('•').red} #{Rainbow('"Process manager not found"').white} #{Rainbow('→ Install:').yellow} #{Rainbow('brew install overmind').green.bold} #{Rainbow('(or').white} #{Rainbow('gem install foreman').green.bold}#{Rainbow(')').white}
|
321
|
+
#{Rainbow('•').red} #{Rainbow('"Assets not loading"').white} #{Rainbow('→ Verify Procfile.dev is present and check server logs').white}
|
322
|
+
|
323
|
+
#{Rainbow('📚 Need help? Visit:').blue.bold} #{Rainbow('https://www.shakacode.com/react-on-rails/docs/').cyan.underline}
|
324
|
+
TROUBLESHOOTING
|
325
|
+
end
|
326
|
+
# rubocop:enable Metrics/AbcSize
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "dev/server_manager"
|
4
|
+
require_relative "dev/process_manager"
|
5
|
+
require_relative "dev/pack_generator"
|
6
|
+
require_relative "dev/file_manager"
|
7
|
+
|
8
|
+
module ReactOnRails
|
9
|
+
module Dev
|
10
|
+
# Development server management for React on Rails
|
11
|
+
#
|
12
|
+
# This module provides classes to manage development servers,
|
13
|
+
# process managers, pack generation, and file cleanup.
|
14
|
+
#
|
15
|
+
# Usage:
|
16
|
+
# ReactOnRails::Dev::ServerManager.start(:development)
|
17
|
+
# ReactOnRails::Dev::ServerManager.kill_processes
|
18
|
+
# ReactOnRails::Dev::ServerManager.show_help
|
19
|
+
end
|
20
|
+
end
|
@@ -8,5 +8,11 @@ module ReactOnRails
|
|
8
8
|
VersionChecker.build.log_if_gem_and_node_package_versions_differ
|
9
9
|
ReactOnRails::ServerRenderingPool.reset_pool
|
10
10
|
end
|
11
|
+
|
12
|
+
rake_tasks do
|
13
|
+
load File.expand_path("../tasks/generate_packs.rake", __dir__)
|
14
|
+
load File.expand_path("../tasks/assets.rake", __dir__)
|
15
|
+
load File.expand_path("../tasks/locale.rake", __dir__)
|
16
|
+
end
|
11
17
|
end
|
12
18
|
end
|
@@ -11,9 +11,19 @@ module ReactOnRails
|
|
11
11
|
return false if git_installed && status&.empty?
|
12
12
|
|
13
13
|
error = if git_installed
|
14
|
-
|
14
|
+
<<~MSG.strip
|
15
|
+
You have uncommitted changes. Please commit or stash them before continuing.
|
16
|
+
|
17
|
+
The React on Rails generator creates many new files and it's important to keep
|
18
|
+
your existing changes separate from the generated code for easier review.
|
19
|
+
MSG
|
15
20
|
else
|
16
|
-
|
21
|
+
<<~MSG.strip
|
22
|
+
Git is not installed. Please install Git and commit your changes before continuing.
|
23
|
+
|
24
|
+
The React on Rails generator creates many new files and version control helps
|
25
|
+
track what was generated versus your existing code.
|
26
|
+
MSG
|
17
27
|
end
|
18
28
|
message_handler.add_error(error)
|
19
29
|
true
|
@@ -17,6 +17,9 @@ module ReactOnRails
|
|
17
17
|
include ReactOnRails::Utils::Required
|
18
18
|
|
19
19
|
COMPONENT_HTML_KEY = "componentHtml"
|
20
|
+
IMMEDIATE_HYDRATION_PRO_WARNING = "[REACT ON RAILS] The 'immediate_hydration' feature requires a " \
|
21
|
+
"React on Rails Pro license. " \
|
22
|
+
"Please visit https://shakacode.com/react-on-rails-pro to learn more."
|
20
23
|
|
21
24
|
# react_component_name: can be a React function or class component or a "Render-Function".
|
22
25
|
# "Render-Functions" differ from a React function in that they take two parameters, the
|
@@ -58,15 +61,17 @@ module ReactOnRails
|
|
58
61
|
server_rendered_html = internal_result[:result]["html"]
|
59
62
|
console_script = internal_result[:result]["consoleReplayScript"]
|
60
63
|
render_options = internal_result[:render_options]
|
64
|
+
badge = pro_warning_badge_if_needed(internal_result[:immediate_hydration_requested])
|
61
65
|
|
62
66
|
case server_rendered_html
|
63
67
|
when String
|
64
|
-
build_react_component_result_for_server_rendered_string(
|
68
|
+
html = build_react_component_result_for_server_rendered_string(
|
65
69
|
server_rendered_html: server_rendered_html,
|
66
70
|
component_specification_tag: internal_result[:tag],
|
67
71
|
console_script: console_script,
|
68
72
|
render_options: render_options
|
69
73
|
)
|
74
|
+
(badge + html).html_safe
|
70
75
|
when Hash
|
71
76
|
msg = <<~MSG
|
72
77
|
Use react_component_hash (not react_component) to return a Hash to your ruby view code. See
|
@@ -126,7 +131,7 @@ module ReactOnRails
|
|
126
131
|
# stream_react_component doesn't have the prerender option
|
127
132
|
# Because setting prerender to false is equivalent to calling react_component with prerender: false
|
128
133
|
options[:prerender] = true
|
129
|
-
options = options.merge(
|
134
|
+
options = options.merge(immediate_hydration: true) unless options.key?(:immediate_hydration)
|
130
135
|
run_stream_inside_fiber do
|
131
136
|
internal_stream_react_component(component_name, options)
|
132
137
|
end
|
@@ -208,22 +213,26 @@ module ReactOnRails
|
|
208
213
|
#
|
209
214
|
def react_component_hash(component_name, options = {})
|
210
215
|
options[:prerender] = true
|
216
|
+
|
211
217
|
internal_result = internal_react_component(component_name, options)
|
212
218
|
server_rendered_html = internal_result[:result]["html"]
|
213
219
|
console_script = internal_result[:result]["consoleReplayScript"]
|
214
220
|
render_options = internal_result[:render_options]
|
221
|
+
badge = pro_warning_badge_if_needed(internal_result[:immediate_hydration_requested])
|
215
222
|
|
216
223
|
if server_rendered_html.is_a?(String) && internal_result[:result]["hasErrors"]
|
217
224
|
server_rendered_html = { COMPONENT_HTML_KEY => internal_result[:result]["html"] }
|
218
225
|
end
|
219
226
|
|
220
227
|
if server_rendered_html.is_a?(Hash)
|
221
|
-
build_react_component_result_for_server_rendered_hash(
|
228
|
+
result = build_react_component_result_for_server_rendered_hash(
|
222
229
|
server_rendered_html: server_rendered_html,
|
223
230
|
component_specification_tag: internal_result[:tag],
|
224
231
|
console_script: console_script,
|
225
232
|
render_options: render_options
|
226
233
|
)
|
234
|
+
result[COMPONENT_HTML_KEY] = badge + result[COMPONENT_HTML_KEY]
|
235
|
+
result
|
227
236
|
else
|
228
237
|
msg = <<~MSG
|
229
238
|
Render-Function used by react_component_hash for #{component_name} is expected to return
|
@@ -247,13 +256,16 @@ module ReactOnRails
|
|
247
256
|
# props: Ruby Hash or JSON string which contains the properties to pass to the redux store.
|
248
257
|
# Options
|
249
258
|
# defer: false -- pass as true if you wish to render this below your component.
|
250
|
-
#
|
251
|
-
# waiting for the page to load.
|
252
|
-
def redux_store(store_name, props: {}, defer: false,
|
253
|
-
|
259
|
+
# immediate_hydration: false -- React on Rails Pro (licensed) feature. Pass as true if you wish to
|
260
|
+
# hydrate this store immediately instead of waiting for the page to load.
|
261
|
+
def redux_store(store_name, props: {}, defer: false, immediate_hydration: nil)
|
262
|
+
immediate_hydration = ReactOnRails.configuration.immediate_hydration if immediate_hydration.nil?
|
263
|
+
badge = pro_warning_badge_if_needed(immediate_hydration)
|
264
|
+
immediate_hydration = false unless support_pro_features?
|
265
|
+
|
254
266
|
redux_store_data = { store_name: store_name,
|
255
267
|
props: props,
|
256
|
-
|
268
|
+
immediate_hydration: immediate_hydration }
|
257
269
|
if defer
|
258
270
|
registered_stores_defer_render << redux_store_data
|
259
271
|
"YOU SHOULD NOT SEE THIS ON YOUR VIEW -- Uses as a code block, like <% redux_store %> " \
|
@@ -261,7 +273,7 @@ module ReactOnRails
|
|
261
273
|
else
|
262
274
|
registered_stores << redux_store_data
|
263
275
|
result = render_redux_store_data(redux_store_data)
|
264
|
-
prepend_render_rails_context(result)
|
276
|
+
(badge + prepend_render_rails_context(result)).html_safe
|
265
277
|
end
|
266
278
|
end
|
267
279
|
|
@@ -334,7 +346,7 @@ module ReactOnRails
|
|
334
346
|
|
335
347
|
html = result["html"]
|
336
348
|
console_log_script = result["consoleLogScript"]
|
337
|
-
raw("#{html}#{render_options.replay_console
|
349
|
+
raw("#{html}#{console_log_script if render_options.replay_console}")
|
338
350
|
rescue ExecJS::ProgramError => err
|
339
351
|
raise ReactOnRails::PrerenderError.new(component_name: "N/A (server_render_js called)",
|
340
352
|
err: err,
|
@@ -401,7 +413,7 @@ module ReactOnRails
|
|
401
413
|
result.merge!(
|
402
414
|
# URL settings
|
403
415
|
href: uri.to_s,
|
404
|
-
location: "#{uri.path}#{
|
416
|
+
location: "#{uri.path}#{"?#{uri.query}" if uri.query.present?}",
|
405
417
|
scheme: uri.scheme, # http
|
406
418
|
host: uri.host, # foo.com
|
407
419
|
port: uri.port,
|
@@ -440,7 +452,32 @@ module ReactOnRails
|
|
440
452
|
|
441
453
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
442
454
|
|
443
|
-
|
455
|
+
# Checks if React on Rails Pro features are available
|
456
|
+
# @return [Boolean] true if Pro license is valid, false otherwise
|
457
|
+
def support_pro_features?
|
458
|
+
ReactOnRails::Utils.react_on_rails_pro_licence_valid?
|
459
|
+
end
|
460
|
+
|
461
|
+
def pro_warning_badge_if_needed(immediate_hydration)
|
462
|
+
return "".html_safe unless immediate_hydration
|
463
|
+
return "".html_safe if support_pro_features?
|
464
|
+
|
465
|
+
puts IMMEDIATE_HYDRATION_PRO_WARNING
|
466
|
+
Rails.logger.warn IMMEDIATE_HYDRATION_PRO_WARNING
|
467
|
+
|
468
|
+
tooltip_text = "The 'immediate_hydration' feature requires a React on Rails Pro license. Click to learn more."
|
469
|
+
|
470
|
+
badge_html = <<~HTML
|
471
|
+
<a href="https://shakacode.com/react-on-rails-pro" target="_blank" rel="noopener noreferrer" title="#{tooltip_text}">
|
472
|
+
<div style="position: fixed; top: 0; right: 0; width: 180px; height: 180px; overflow: hidden; z-index: 9999; pointer-events: none;">
|
473
|
+
<div style="position: absolute; top: 50px; right: -40px; transform: rotate(45deg); background-color: rgba(220, 53, 69, 0.85); color: white; padding: 7px 40px; text-align: center; font-weight: bold; font-family: sans-serif; font-size: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.3); pointer-events: auto;">
|
474
|
+
React On Rails Pro Required
|
475
|
+
</div>
|
476
|
+
</div>
|
477
|
+
</a>
|
478
|
+
HTML
|
479
|
+
badge_html.strip.html_safe
|
480
|
+
end
|
444
481
|
|
445
482
|
def run_stream_inside_fiber
|
446
483
|
unless ReactOnRails::Utils.react_on_rails_pro?
|
@@ -638,6 +675,9 @@ module ReactOnRails
|
|
638
675
|
# server has already rendered the HTML.
|
639
676
|
|
640
677
|
render_options = create_render_options(react_component_name, options)
|
678
|
+
# Capture the originally requested value so we can show a badge while still disabling the feature.
|
679
|
+
immediate_hydration_requested = render_options.immediate_hydration
|
680
|
+
render_options.set_option(:immediate_hydration, false) unless support_pro_features?
|
641
681
|
|
642
682
|
# Setup the page_loaded_js, which is the same regardless of prerendering or not!
|
643
683
|
# The reason is that React is smart about not doing extra work if the server rendering did its job.
|
@@ -650,9 +690,10 @@ module ReactOnRails
|
|
650
690
|
"data-trace" => (render_options.trace ? true : nil),
|
651
691
|
"data-dom-id" => render_options.dom_id,
|
652
692
|
"data-store-dependencies" => render_options.store_dependencies&.to_json,
|
653
|
-
"data-
|
693
|
+
"data-immediate-hydration" =>
|
694
|
+
(render_options.immediate_hydration ? true : nil))
|
654
695
|
|
655
|
-
if render_options.
|
696
|
+
if render_options.immediate_hydration
|
656
697
|
component_specification_tag.concat(
|
657
698
|
content_tag(:script, %(
|
658
699
|
typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsComponentLoaded('#{render_options.dom_id}');
|
@@ -667,7 +708,8 @@ typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsComponentLoaded('#{
|
|
667
708
|
{
|
668
709
|
render_options: render_options,
|
669
710
|
tag: component_specification_tag,
|
670
|
-
result: result
|
711
|
+
result: result,
|
712
|
+
immediate_hydration_requested: immediate_hydration_requested
|
671
713
|
}
|
672
714
|
end
|
673
715
|
|
@@ -676,9 +718,10 @@ typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsComponentLoaded('#{
|
|
676
718
|
json_safe_and_pretty(redux_store_data[:props]).html_safe,
|
677
719
|
type: "application/json",
|
678
720
|
"data-js-react-on-rails-store" => redux_store_data[:store_name].html_safe,
|
679
|
-
"data-
|
721
|
+
"data-immediate-hydration" =>
|
722
|
+
(redux_store_data[:immediate_hydration] ? true : nil))
|
680
723
|
|
681
|
-
if redux_store_data[:
|
724
|
+
if redux_store_data[:immediate_hydration]
|
682
725
|
store_hydration_data.concat(
|
683
726
|
content_tag(:script, <<~JS.strip_heredoc.html_safe
|
684
727
|
typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsStoreLoaded('#{redux_store_data[:store_name]}');
|
@@ -814,6 +857,7 @@ typeof ReactOnRails === 'object' && ReactOnRails.reactOnRailsComponentLoaded('#{
|
|
814
857
|
|
815
858
|
if defined?(ScoutApm)
|
816
859
|
include ScoutApm::Tracer
|
860
|
+
|
817
861
|
instrument_method :react_component, type: "ReactOnRails", name: "react_component"
|
818
862
|
instrument_method :react_component_hash, type: "ReactOnRails", name: "react_component_hash"
|
819
863
|
end
|
@@ -3,27 +3,18 @@
|
|
3
3
|
module ReactOnRails
|
4
4
|
module PackerUtils
|
5
5
|
def self.using_packer?
|
6
|
-
using_shakapacker_const?
|
6
|
+
using_shakapacker_const?
|
7
7
|
end
|
8
8
|
|
9
9
|
def self.using_shakapacker_const?
|
10
10
|
return @using_shakapacker_const if defined?(@using_shakapacker_const)
|
11
11
|
|
12
12
|
@using_shakapacker_const = ReactOnRails::Utils.gem_available?("shakapacker") &&
|
13
|
-
shakapacker_version_requirement_met?("
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.using_webpacker_const?
|
17
|
-
return @using_webpacker_const if defined?(@using_webpacker_const)
|
18
|
-
|
19
|
-
@using_webpacker_const = (ReactOnRails::Utils.gem_available?("shakapacker") &&
|
20
|
-
shakapacker_version_as_array[0] <= 6) ||
|
21
|
-
ReactOnRails::Utils.gem_available?("webpacker")
|
13
|
+
shakapacker_version_requirement_met?("8.2.0")
|
22
14
|
end
|
23
15
|
|
24
16
|
def self.packer_type
|
25
17
|
return "shakapacker" if using_shakapacker_const?
|
26
|
-
return "webpacker" if using_webpacker_const?
|
27
18
|
|
28
19
|
nil
|
29
20
|
end
|
@@ -31,12 +22,8 @@ module ReactOnRails
|
|
31
22
|
def self.packer
|
32
23
|
return nil unless using_packer?
|
33
24
|
|
34
|
-
|
35
|
-
|
36
|
-
return ::Shakapacker
|
37
|
-
end
|
38
|
-
require "webpacker"
|
39
|
-
::Webpacker
|
25
|
+
require "shakapacker"
|
26
|
+
::Shakapacker
|
40
27
|
end
|
41
28
|
|
42
29
|
def self.dev_server_running?
|
@@ -106,7 +93,6 @@ module ReactOnRails
|
|
106
93
|
end
|
107
94
|
|
108
95
|
def self.precompile?
|
109
|
-
return ::Webpacker.config.webpacker_precompile? if using_webpacker_const?
|
110
96
|
return ::Shakapacker.config.shakapacker_precompile? if using_shakapacker_const?
|
111
97
|
|
112
98
|
false
|