react_on_rails 16.4.0.rc.9 → 16.4.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/Gemfile.lock +1 -1
- data/lib/generators/react_on_rails/base_generator.rb +373 -23
- data/lib/generators/react_on_rails/generator_messages.rb +18 -17
- data/lib/generators/react_on_rails/install_generator.rb +85 -9
- data/lib/generators/react_on_rails/react_with_redux_generator.rb +7 -0
- data/lib/generators/react_on_rails/rsc_setup.rb +228 -1
- data/lib/generators/react_on_rails/templates/base/base/app/controllers/hello_world_controller.rb +1 -1
- data/lib/generators/react_on_rails/templates/base/base/app/views/layouts/{hello_world.html.erb → react_on_rails_default.html.erb} +1 -1
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/rspack.config.js.tt +15 -0
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/rspack.config.ts.tt +15 -0
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/webpack.config.ts.tt +15 -0
- data/lib/generators/react_on_rails/templates/rsc/base/app/controllers/{hello_server_controller.rb → hello_server_controller.rb.tt} +1 -1
- data/lib/generators/react_on_rails/templates/rsc/base/app/views/hello_server/index.html.erb +1 -2
- data/lib/react_on_rails/configuration.rb +4 -4
- data/lib/react_on_rails/helper.rb +51 -38
- data/lib/react_on_rails/locales/base.rb +2 -2
- data/lib/react_on_rails/locales/to_js.rb +2 -2
- data/lib/react_on_rails/packer_utils.rb +4 -4
- data/lib/react_on_rails/pro_helper.rb +1 -1
- data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +1 -1
- data/lib/react_on_rails/system_checker.rb +101 -28
- data/lib/react_on_rails/test_helper.rb +1 -1
- data/lib/react_on_rails/utils.rb +1 -1
- data/lib/react_on_rails/version.rb +1 -1
- data/rakelib/update_changelog.rake +146 -28
- metadata +7 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2e8711eddc91f2c2afd343c203a466e320e3c98231a6cea82f950f91de9c234c
|
|
4
|
+
data.tar.gz: 3e833073ef81b6051e2484075288e7aca18026eafb9d7e1727a84a7ae53e22fc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fb4f879f38e2a7fce93204c22efaf2c360dc5031ad52384d7abe3cfcf0f712365dbfed53ae3385f93106d4d9549bb36a714caacb820d633d7c21a4864dce3c00
|
|
7
|
+
data.tar.gz: df85baa5aecb849d6b28636975540a2102ab0f70c1efdc3b00e26923dd4524f4bda758a6153eb541c8db86dfc8e6860830c6336785e50c1d5416d12486b2d5e7
|
data/Gemfile.lock
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "rails/generators"
|
|
4
4
|
require "fileutils"
|
|
5
|
+
require "erb"
|
|
5
6
|
require_relative "generator_messages"
|
|
6
7
|
require_relative "generator_helper"
|
|
7
8
|
require_relative "js_dependency_manager"
|
|
@@ -47,6 +48,56 @@ module ReactOnRails
|
|
|
47
48
|
default: false,
|
|
48
49
|
hide: true
|
|
49
50
|
|
|
51
|
+
# Keep this map in sync with templates under:
|
|
52
|
+
# lib/generators/react_on_rails/templates/**/config/webpack/*.tt
|
|
53
|
+
MANAGED_WEBPACK_FILE_TEMPLATES = {
|
|
54
|
+
"clientWebpackConfig.js" => "base/base/config/webpack/clientWebpackConfig.js.tt",
|
|
55
|
+
"commonWebpackConfig.js" => "base/base/config/webpack/commonWebpackConfig.js.tt",
|
|
56
|
+
"test.js" => "base/base/config/webpack/test.js.tt",
|
|
57
|
+
"development.js" => "base/base/config/webpack/development.js.tt",
|
|
58
|
+
"production.js" => "base/base/config/webpack/production.js.tt",
|
|
59
|
+
"serverWebpackConfig.js" => "base/base/config/webpack/serverWebpackConfig.js.tt",
|
|
60
|
+
"rscWebpackConfig.js" => "rsc/base/config/webpack/rscWebpackConfig.js.tt",
|
|
61
|
+
"ServerClientOrBoth.js" => "base/base/config/webpack/ServerClientOrBoth.js.tt",
|
|
62
|
+
# Legacy filename generated by older React on Rails versions.
|
|
63
|
+
# We compare against the current options (including --pro/--rsc); if those
|
|
64
|
+
# don't match the original generation, cleanup intentionally preserves files.
|
|
65
|
+
"generateWebpackConfigs.js" => "base/base/config/webpack/ServerClientOrBoth.js.tt"
|
|
66
|
+
}.freeze
|
|
67
|
+
|
|
68
|
+
# Keep these helper delegates in sync with ERB calls in managed webpack
|
|
69
|
+
# templates. This is the intended template-facing API; missing delegates
|
|
70
|
+
# intentionally fail rendering and keep cleanup conservative by treating
|
|
71
|
+
# files as non-removable.
|
|
72
|
+
TemplateRenderContext = Struct.new(:generator, :config) do
|
|
73
|
+
def erb_binding
|
|
74
|
+
binding
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def add_documentation_reference(*args)
|
|
78
|
+
generator.__send__(:add_documentation_reference, *args)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def use_pro?
|
|
82
|
+
generator.__send__(:use_pro?)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def use_rsc?
|
|
86
|
+
generator.__send__(:use_rsc?)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def shakapacker_version_9_or_higher?
|
|
90
|
+
generator.__send__(:shakapacker_version_9_or_higher?)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
REMOVABLE_WEBPACK_FILES = (MANAGED_WEBPACK_FILE_TEMPLATES.keys +
|
|
95
|
+
%w[webpack.config.js]).freeze
|
|
96
|
+
DOCS_REFERENCE_MESSAGE = "// The source code including full typescript support is available at:"
|
|
97
|
+
TEMPLATE_RENDER_FAILED = Object.new.freeze # unique sentinel compared by identity via .equal?
|
|
98
|
+
private_constant :MANAGED_WEBPACK_FILE_TEMPLATES, :REMOVABLE_WEBPACK_FILES, :TemplateRenderContext,
|
|
99
|
+
:DOCS_REFERENCE_MESSAGE, :TEMPLATE_RENDER_FAILED
|
|
100
|
+
|
|
50
101
|
def add_hello_world_route
|
|
51
102
|
# RSC uses HelloServer instead of HelloWorld, but Redux still needs hello_world route
|
|
52
103
|
return if use_rsc? && !options.redux?
|
|
@@ -70,9 +121,9 @@ module ReactOnRails
|
|
|
70
121
|
.env.example
|
|
71
122
|
bin/shakapacker-precompile-hook]
|
|
72
123
|
|
|
73
|
-
#
|
|
74
|
-
# packs without requiring a hardcoded application.js pack entry.
|
|
75
|
-
base_files << "app/views/layouts/
|
|
124
|
+
# react_on_rails_default layout provides empty pack tags so React on Rails can
|
|
125
|
+
# inject generated packs without requiring a hardcoded application.js pack entry.
|
|
126
|
+
base_files << "app/views/layouts/react_on_rails_default.html.erb"
|
|
76
127
|
|
|
77
128
|
# HelloWorld controller only when not using RSC (RSC uses HelloServer)
|
|
78
129
|
# Exception: Redux still needs the HelloWorld controller even with RSC
|
|
@@ -104,6 +155,10 @@ module ReactOnRails
|
|
|
104
155
|
end
|
|
105
156
|
|
|
106
157
|
def copy_webpack_config
|
|
158
|
+
# Cleanup must run before writing new webpack/rspack configs so we only
|
|
159
|
+
# evaluate pre-existing stale entries, never files generated in this run.
|
|
160
|
+
cleanup_stale_webpack_config_dir_for_rspack
|
|
161
|
+
|
|
107
162
|
say "Adding #{using_rspack? ? 'Rspack' : 'Webpack'} config"
|
|
108
163
|
base_path = "base/base"
|
|
109
164
|
base_files = %w[babel.config.js
|
|
@@ -114,9 +169,7 @@ module ReactOnRails
|
|
|
114
169
|
config/webpack/production.js
|
|
115
170
|
config/webpack/serverWebpackConfig.js
|
|
116
171
|
config/webpack/ServerClientOrBoth.js]
|
|
117
|
-
config = {
|
|
118
|
-
message: "// The source code including full typescript support is available at:"
|
|
119
|
-
}
|
|
172
|
+
config = { message: DOCS_REFERENCE_MESSAGE }
|
|
120
173
|
base_files.each do |file|
|
|
121
174
|
template("#{base_path}/#{file}.tt", destination_config_path(file), config)
|
|
122
175
|
end
|
|
@@ -183,7 +236,7 @@ module ReactOnRails
|
|
|
183
236
|
add_configure_minitest_to_compile_assets(test_helper) if File.exist?(test_helper)
|
|
184
237
|
end
|
|
185
238
|
|
|
186
|
-
CONFIGURE_RSPEC_TO_COMPILE_ASSETS =
|
|
239
|
+
CONFIGURE_RSPEC_TO_COMPILE_ASSETS = <<~STR
|
|
187
240
|
RSpec.configure do |config|
|
|
188
241
|
# Ensure that if we are running js tests, we are using latest webpack assets
|
|
189
242
|
# This will use the defaults of :js and :server_rendering meta tags
|
|
@@ -193,7 +246,7 @@ module ReactOnRails
|
|
|
193
246
|
end
|
|
194
247
|
STR
|
|
195
248
|
|
|
196
|
-
CONFIGURE_MINITEST_TO_COMPILE_ASSETS =
|
|
249
|
+
CONFIGURE_MINITEST_TO_COMPILE_ASSETS = <<~STR
|
|
197
250
|
# Ensure that tests run against fresh webpack assets.
|
|
198
251
|
ActiveSupport::TestCase.setup do
|
|
199
252
|
ReactOnRails::TestHelper.ensure_assets_compiled
|
|
@@ -214,19 +267,20 @@ module ReactOnRails
|
|
|
214
267
|
|
|
215
268
|
def copy_webpack_main_config(base_path, config)
|
|
216
269
|
webpack_config_path = bundler_main_config_path
|
|
270
|
+
template_path = bundler_main_config_template_path(base_path, webpack_config_path)
|
|
217
271
|
|
|
218
272
|
if File.exist?(webpack_config_path)
|
|
219
273
|
existing_content = File.read(webpack_config_path)
|
|
220
274
|
|
|
221
275
|
# Check if it's the standard Shakapacker config that we can safely replace
|
|
222
|
-
if standard_shakapacker_config?(existing_content)
|
|
276
|
+
if standard_shakapacker_config?(existing_content, strip_comments: true)
|
|
223
277
|
# Remove the file first to avoid conflict prompt, then recreate it
|
|
224
278
|
remove_file(webpack_config_path, verbose: false)
|
|
225
279
|
# Show what we're doing
|
|
226
280
|
say_status :replace,
|
|
227
281
|
"#{webpack_config_path} (auto-upgrading from standard Shakapacker to React on Rails config)",
|
|
228
282
|
:green
|
|
229
|
-
template(
|
|
283
|
+
template(template_path, webpack_config_path, config)
|
|
230
284
|
elsif react_on_rails_config?(existing_content)
|
|
231
285
|
say_status :identical, "#{webpack_config_path} (already React on Rails compatible)", :blue
|
|
232
286
|
# Skip - don't need to do anything
|
|
@@ -235,7 +289,7 @@ module ReactOnRails
|
|
|
235
289
|
end
|
|
236
290
|
else
|
|
237
291
|
# File doesn't exist, create it
|
|
238
|
-
template(
|
|
292
|
+
template(template_path, webpack_config_path, config)
|
|
239
293
|
end
|
|
240
294
|
end
|
|
241
295
|
|
|
@@ -256,7 +310,7 @@ module ReactOnRails
|
|
|
256
310
|
say_status :create, "#{backup_path} (backup of your custom config)", :green
|
|
257
311
|
end
|
|
258
312
|
|
|
259
|
-
template(
|
|
313
|
+
template(bundler_main_config_template_path(base_path, webpack_config_path), webpack_config_path, config)
|
|
260
314
|
else
|
|
261
315
|
say_status :skip, webpack_config_path, :yellow
|
|
262
316
|
say_status :warning,
|
|
@@ -267,34 +321,296 @@ module ReactOnRails
|
|
|
267
321
|
|
|
268
322
|
def bundler_main_config_path
|
|
269
323
|
if using_rspack?
|
|
270
|
-
"config/rspack/rspack.config.
|
|
324
|
+
if File.exist?("config/rspack/rspack.config.ts")
|
|
325
|
+
"config/rspack/rspack.config.ts"
|
|
326
|
+
else
|
|
327
|
+
"config/rspack/rspack.config.js"
|
|
328
|
+
end
|
|
329
|
+
elsif File.exist?("config/webpack/webpack.config.ts")
|
|
330
|
+
"config/webpack/webpack.config.ts"
|
|
271
331
|
else
|
|
272
332
|
"config/webpack/webpack.config.js"
|
|
273
333
|
end
|
|
274
334
|
end
|
|
275
335
|
|
|
276
|
-
def
|
|
336
|
+
def cleanup_stale_webpack_config_dir_for_rspack
|
|
337
|
+
return unless cleanup_stale_webpack_config_dir?
|
|
338
|
+
|
|
339
|
+
webpack_config_relative_dir = "config/webpack"
|
|
340
|
+
all_entries = stale_webpack_config_entries
|
|
341
|
+
unless all_entries
|
|
342
|
+
say_status :warning,
|
|
343
|
+
"Keeping #{webpack_config_relative_dir}; could not read directory entries " \
|
|
344
|
+
"(permission denied or path changed)",
|
|
345
|
+
:yellow
|
|
346
|
+
return
|
|
347
|
+
end
|
|
348
|
+
if all_entries.empty?
|
|
349
|
+
say_status :skip, "#{webpack_config_relative_dir} (empty directory, leaving as-is)", :yellow
|
|
350
|
+
return
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
return warn_dotfiles_only_webpack_dir(webpack_config_relative_dir, all_entries) if all_dotfiles?(all_entries)
|
|
354
|
+
|
|
355
|
+
# We only clean up known top-level files. Any directory or unknown entry is
|
|
356
|
+
# treated as non-removable so we don't recurse into user-managed content.
|
|
357
|
+
non_removable_entries = non_removable_webpack_entries(all_entries)
|
|
358
|
+
removable_entries = removable_webpack_entries(all_entries)
|
|
359
|
+
if non_removable_entries.empty?
|
|
360
|
+
remove_stale_webpack_dir(webpack_config_relative_dir)
|
|
361
|
+
return
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
if all_dotfiles?(non_removable_entries)
|
|
365
|
+
handle_dotfile_only_non_removable_entries(
|
|
366
|
+
webpack_config_relative_dir,
|
|
367
|
+
removable_entries,
|
|
368
|
+
non_removable_entries
|
|
369
|
+
)
|
|
370
|
+
return
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
removed_entries = remove_stale_webpack_files(webpack_config_relative_dir, removable_entries)
|
|
374
|
+
warn_non_removable_webpack_entries(webpack_config_relative_dir, non_removable_entries, removed_entries)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def remove_stale_webpack_dir(webpack_config_relative_dir)
|
|
378
|
+
# Thor's remove_dir uses paths relative to destination_root.
|
|
379
|
+
# TOCTOU: a file created between the removability check and remove_dir
|
|
380
|
+
# would be deleted. Acceptable for this generator cleanup path.
|
|
381
|
+
remove_dir(webpack_config_relative_dir, verbose: false)
|
|
382
|
+
say_status :remove,
|
|
383
|
+
"#{webpack_config_relative_dir} (stale webpack configs after switching to --rspack)",
|
|
384
|
+
:green
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
def handle_dotfile_only_non_removable_entries(
|
|
388
|
+
webpack_config_relative_dir,
|
|
389
|
+
removable_entries,
|
|
390
|
+
non_removable_entries
|
|
391
|
+
)
|
|
392
|
+
removed_entries = remove_stale_webpack_files(webpack_config_relative_dir, removable_entries)
|
|
393
|
+
warn_dotfiles_in_webpack_dir(webpack_config_relative_dir, non_removable_entries, removed_entries)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def cleanup_stale_webpack_config_dir?
|
|
397
|
+
using_rspack? && Dir.exist?(stale_webpack_config_dir)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def all_dotfiles?(entries)
|
|
401
|
+
entries.any? && entries.all? { |entry| entry.start_with?(".") }
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def warn_dotfiles_only_webpack_dir(webpack_config_relative_dir, all_entries)
|
|
405
|
+
say_status :warning,
|
|
406
|
+
"Keeping #{webpack_config_relative_dir}; only dotfiles found: " \
|
|
407
|
+
"#{all_entries.join(', ')}",
|
|
408
|
+
:yellow
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def warn_non_removable_webpack_entries(webpack_config_relative_dir, non_removable_entries, removed_entries = [])
|
|
412
|
+
non_dotfile_entries = non_removable_entries.reject { |entry| entry.start_with?(".") }
|
|
413
|
+
dotfiles = non_removable_entries.select { |entry| entry.start_with?(".") }
|
|
414
|
+
non_removable_directories, non_removable_files = non_dotfile_entries.partition do |entry|
|
|
415
|
+
directory_entry?(entry)
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
non_removable_sections = []
|
|
419
|
+
non_removable_sections << "files: #{non_removable_files.join(', ')}" if non_removable_files.any?
|
|
420
|
+
if non_removable_directories.any?
|
|
421
|
+
non_removable_sections << "directories: #{non_removable_directories.join(', ')}"
|
|
422
|
+
end
|
|
423
|
+
non_removable_sections << "dotfiles: #{dotfiles.join(', ')}" if dotfiles.any?
|
|
424
|
+
|
|
425
|
+
non_removable_sections_message = non_removable_sections.join("; ")
|
|
426
|
+
removed_entries_message = if removed_entries.any?
|
|
427
|
+
" Removed stale managed files: #{removed_entries.join(', ')}."
|
|
428
|
+
else
|
|
429
|
+
""
|
|
430
|
+
end
|
|
431
|
+
say_status :warning,
|
|
432
|
+
"Keeping #{webpack_config_relative_dir}; " \
|
|
433
|
+
"custom/non-removable entries detected: #{non_removable_sections_message}." \
|
|
434
|
+
"#{removed_entries_message}",
|
|
435
|
+
:yellow
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def directory_entry?(entry)
|
|
439
|
+
File.directory?(File.join(stale_webpack_config_dir, entry))
|
|
440
|
+
rescue Errno::EACCES, Errno::ELOOP, Errno::ENOENT
|
|
441
|
+
false
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def warn_dotfiles_in_webpack_dir(webpack_config_relative_dir, dotfiles, removed_entries)
|
|
445
|
+
removed_entries_message = if removed_entries.any?
|
|
446
|
+
" Removed stale managed files: #{removed_entries.join(', ')}."
|
|
447
|
+
else
|
|
448
|
+
""
|
|
449
|
+
end
|
|
450
|
+
say_status :warning,
|
|
451
|
+
"Keeping #{webpack_config_relative_dir}; dotfiles present: #{dotfiles.join(', ')}. " \
|
|
452
|
+
"Remove them manually if you want the directory cleaned up." \
|
|
453
|
+
"#{removed_entries_message}",
|
|
454
|
+
:yellow
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def non_removable_webpack_entries(all_entries)
|
|
458
|
+
all_entries.select do |entry|
|
|
459
|
+
entry.start_with?(".") || !removable_webpack_entry?(entry)
|
|
460
|
+
end
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
def removable_webpack_entries(all_entries)
|
|
464
|
+
all_entries.select { |entry| removable_webpack_entry?(entry) }
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def remove_stale_webpack_files(webpack_config_relative_dir, entries)
|
|
468
|
+
entries.each_with_object([]) do |entry, removed_entries|
|
|
469
|
+
relative_path = File.join(webpack_config_relative_dir, entry)
|
|
470
|
+
full_path = File.join(destination_root, relative_path)
|
|
471
|
+
remove_file(relative_path, verbose: false)
|
|
472
|
+
removed_entries << entry unless File.exist?(full_path)
|
|
473
|
+
rescue Errno::EACCES, Errno::ELOOP, Errno::ENOENT, Errno::ENOTDIR
|
|
474
|
+
# If we cannot stat after removal attempt, conservatively report as not removed.
|
|
475
|
+
nil
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def removable_webpack_entry?(entry)
|
|
480
|
+
return false unless REMOVABLE_WEBPACK_FILES.include?(entry)
|
|
481
|
+
|
|
482
|
+
full_path = File.join(stale_webpack_config_dir, entry)
|
|
483
|
+
return false if File.symlink?(full_path)
|
|
484
|
+
return false unless File.file?(full_path)
|
|
485
|
+
|
|
486
|
+
content = safe_read_cleanup_file(full_path)
|
|
487
|
+
return false unless content
|
|
488
|
+
|
|
489
|
+
return removable_webpack_main_config?(content) if entry == "webpack.config.js"
|
|
490
|
+
|
|
491
|
+
removable_managed_webpack_entry?(entry, content)
|
|
492
|
+
rescue Errno::EACCES, Errno::ELOOP, Errno::ENOENT, Errno::ENOTDIR
|
|
493
|
+
false
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
def removable_webpack_main_config?(content)
|
|
497
|
+
webpack_template = rendered_template_for_cleanup("base/base/config/webpack/webpack.config.js.tt")
|
|
498
|
+
return standard_shakapacker_config?(content) if webpack_template.equal?(TEMPLATE_RENDER_FAILED)
|
|
499
|
+
|
|
500
|
+
# Cleanup is deliberately comment-sensitive (unlike copy_webpack_main_config)
|
|
501
|
+
# so comment-only edits keep the file as a potential customization.
|
|
502
|
+
standard_shakapacker_config?(content) || content_matches_template?(content, webpack_template)
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def removable_managed_webpack_entry?(entry, content)
|
|
506
|
+
template_path = MANAGED_WEBPACK_FILE_TEMPLATES[entry]
|
|
507
|
+
return false unless template_path
|
|
508
|
+
|
|
509
|
+
rendered_template = rendered_template_for_cleanup(template_path)
|
|
510
|
+
return false if rendered_template.equal?(TEMPLATE_RENDER_FAILED)
|
|
511
|
+
|
|
512
|
+
content_matches_template?(content, rendered_template)
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
def rendered_template_for_cleanup(template_path)
|
|
516
|
+
@rendered_template_cache ||= {}
|
|
517
|
+
@rendered_template_cache[template_path] ||= begin
|
|
518
|
+
# Cleanup comparisons only need the injected documentation comment.
|
|
519
|
+
template_doc_config = { message: DOCS_REFERENCE_MESSAGE }
|
|
520
|
+
template_content = File.read(File.join(self.class.source_root, template_path))
|
|
521
|
+
# Render against current generator options. Any mismatch is treated as non-removable,
|
|
522
|
+
# which is intentional because cleanup should be conservative.
|
|
523
|
+
# Note: files originally generated with --pro or --rsc will not match when the
|
|
524
|
+
# current run omits those options; in that case, we preserve the directory.
|
|
525
|
+
# Templates rely on config[:message] plus a small helper subset exposed by
|
|
526
|
+
# TemplateRenderContext (add_documentation_reference, use_pro?, use_rsc?,
|
|
527
|
+
# shakapacker_version_9_or_higher?). Missing method delegates raise
|
|
528
|
+
# NoMethodError and are caught below, treating the file as non-removable.
|
|
529
|
+
# Missing config hash keys return nil silently, so any new config key
|
|
530
|
+
# required by templates must be added to template_doc_config above.
|
|
531
|
+
# Use TemplateRenderContext#erb_binding to avoid leaking method-local
|
|
532
|
+
# variables from rendered_template_for_cleanup into the ERB scope.
|
|
533
|
+
template_render_context = TemplateRenderContext.new(self, template_doc_config)
|
|
534
|
+
ERB.new(template_content, trim_mode: "-").result(template_render_context.erb_binding)
|
|
535
|
+
rescue StandardError => e
|
|
536
|
+
say_status :warning,
|
|
537
|
+
"Could not render template #{template_path} for cleanup check (#{e.class}: #{e.message}); " \
|
|
538
|
+
"treating as non-removable",
|
|
539
|
+
:yellow
|
|
540
|
+
# Rendering failures should never abort installation. Returning a sentinel
|
|
541
|
+
# guarantees a non-match so the file is treated as non-removable.
|
|
542
|
+
# We cache the sentinel intentionally (same options within one generator run)
|
|
543
|
+
# so repeated checks stay consistent and avoid noisy duplicate warnings.
|
|
544
|
+
TEMPLATE_RENDER_FAILED
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def stale_webpack_config_dir
|
|
549
|
+
File.join(destination_root, "config/webpack")
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
def stale_webpack_config_entries
|
|
553
|
+
Dir.children(stale_webpack_config_dir).sort
|
|
554
|
+
rescue Errno::EACCES, Errno::ENOENT, Errno::ENOTDIR
|
|
555
|
+
# TOCTOU: directory may disappear between exist? check and read
|
|
556
|
+
nil
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
def safe_read_cleanup_file(path)
|
|
560
|
+
File.read(path)
|
|
561
|
+
rescue Errno::EACCES, Errno::ELOOP, Errno::ENOENT, Errno::EISDIR, Errno::ENOTDIR
|
|
562
|
+
nil
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
def standard_shakapacker_config?(content, strip_comments: false)
|
|
566
|
+
# Keep strip_comments false by default so comment-only edits are treated as
|
|
567
|
+
# potential customizations during cleanup. Callers can pass true when they
|
|
568
|
+
# need historical comment-insensitive replacement matching.
|
|
277
569
|
# Get the expected default config based on Shakapacker version
|
|
278
570
|
expected_configs = shakapacker_default_configs
|
|
279
571
|
|
|
280
572
|
# Check if the content matches any of the known default configurations
|
|
281
|
-
expected_configs.any? { |config| content_matches_template?(content, config) }
|
|
573
|
+
expected_configs.any? { |config| content_matches_template?(content, config, strip_comments: strip_comments) }
|
|
282
574
|
end
|
|
283
575
|
|
|
284
|
-
def content_matches_template?(content, template)
|
|
576
|
+
def content_matches_template?(content, template, strip_comments: false)
|
|
285
577
|
# Normalize whitespace and compare
|
|
286
|
-
normalize_config_content(content) ==
|
|
578
|
+
normalize_config_content(content, strip_comments: strip_comments) ==
|
|
579
|
+
normalize_config_content(template, strip_comments: strip_comments)
|
|
287
580
|
end
|
|
288
581
|
|
|
289
|
-
def normalize_config_content(content)
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
582
|
+
def normalize_config_content(content, strip_comments: false)
|
|
583
|
+
normalized_content = content
|
|
584
|
+
if strip_comments
|
|
585
|
+
# Replacement detection historically removed inline // comments too.
|
|
586
|
+
# Note: this also strips `//` in string literals (for example URLs),
|
|
587
|
+
# which can yield false-negatives and prompt for replacement.
|
|
588
|
+
normalized_content = normalized_content.gsub(%r{//.*$}, "")
|
|
589
|
+
.gsub(%r{/\*.*?\*/}m, "")
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
# Normalize whitespace while preserving comments by default so added comments
|
|
593
|
+
# count as potential customizations and keep cleanup conservative.
|
|
594
|
+
normalized_content.gsub(/\s+/, " ")
|
|
595
|
+
.tr('"', "'") # Normalize quote style for import/require statements
|
|
596
|
+
.strip
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def bundler_main_config_template_path(base_path, config_path)
|
|
600
|
+
template_ext = config_path.end_with?(".ts") ? "ts.tt" : "js.tt"
|
|
601
|
+
template_base = if config_path.include?("/rspack/") || File.basename(config_path).start_with?("rspack.config")
|
|
602
|
+
"rspack.config"
|
|
603
|
+
else
|
|
604
|
+
"webpack.config"
|
|
605
|
+
end
|
|
606
|
+
"#{base_path}/config/webpack/#{template_base}.#{template_ext}"
|
|
295
607
|
end
|
|
296
608
|
|
|
297
609
|
def shakapacker_default_configs
|
|
610
|
+
shakapacker_cjs_default_configs + shakapacker_esm_default_configs
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
def shakapacker_cjs_default_configs
|
|
298
614
|
configs = []
|
|
299
615
|
|
|
300
616
|
# Shakapacker v7+ (generateWebpackConfig function)
|
|
@@ -347,6 +663,40 @@ module ReactOnRails
|
|
|
347
663
|
configs
|
|
348
664
|
end
|
|
349
665
|
|
|
666
|
+
def shakapacker_esm_default_configs
|
|
667
|
+
configs = []
|
|
668
|
+
|
|
669
|
+
# Shakapacker v9.4+ TypeScript webpack configs (ESM syntax)
|
|
670
|
+
configs << <<~CONFIG
|
|
671
|
+
import { generateWebpackConfig } from 'shakapacker'
|
|
672
|
+
import type { Configuration } from 'webpack'
|
|
673
|
+
const webpackConfig: Configuration = generateWebpackConfig()
|
|
674
|
+
export default webpackConfig
|
|
675
|
+
CONFIG
|
|
676
|
+
|
|
677
|
+
configs << <<~CONFIG
|
|
678
|
+
import { generateWebpackConfig } from 'shakapacker'
|
|
679
|
+
const webpackConfig = generateWebpackConfig()
|
|
680
|
+
export default webpackConfig
|
|
681
|
+
CONFIG
|
|
682
|
+
|
|
683
|
+
# Shakapacker v9.4+ TypeScript rspack configs (ESM syntax)
|
|
684
|
+
configs << <<~CONFIG
|
|
685
|
+
import { generateRspackConfig } from 'shakapacker/rspack'
|
|
686
|
+
import type { RspackOptions } from '@rspack/core'
|
|
687
|
+
const rspackConfig: RspackOptions = generateRspackConfig()
|
|
688
|
+
export default rspackConfig
|
|
689
|
+
CONFIG
|
|
690
|
+
|
|
691
|
+
configs << <<~CONFIG
|
|
692
|
+
import { generateRspackConfig } from 'shakapacker/rspack'
|
|
693
|
+
const rspackConfig = generateRspackConfig()
|
|
694
|
+
export default rspackConfig
|
|
695
|
+
CONFIG
|
|
696
|
+
|
|
697
|
+
configs
|
|
698
|
+
end
|
|
699
|
+
|
|
350
700
|
def react_on_rails_config?(content)
|
|
351
701
|
# Check if it already has React on Rails environment-specific loading
|
|
352
702
|
content.include?("envSpecificConfig") || content.include?("env.nodeEnv")
|
|
@@ -47,11 +47,7 @@ module GeneratorMessages
|
|
|
47
47
|
package_manager = detect_package_manager
|
|
48
48
|
shakapacker_status = build_shakapacker_status_section(shakapacker_just_installed: shakapacker_just_installed)
|
|
49
49
|
render_example = build_render_example(component_name: component_name, route: route, rsc: rsc)
|
|
50
|
-
render_label =
|
|
51
|
-
"• Streaming server rendering in app/views/#{route}/index.html.erb:"
|
|
52
|
-
else
|
|
53
|
-
"• Server-side rendering - Enabled with prerender option in app/views/#{route}/index.html.erb:"
|
|
54
|
-
end
|
|
50
|
+
render_label = build_render_label(route: route, rsc: rsc)
|
|
55
51
|
|
|
56
52
|
<<~MSG
|
|
57
53
|
|
|
@@ -90,6 +86,18 @@ module GeneratorMessages
|
|
|
90
86
|
MSG
|
|
91
87
|
end
|
|
92
88
|
|
|
89
|
+
# Uses relative lockfile paths resolved against Dir.pwd, so callers must invoke
|
|
90
|
+
# this while the current working directory is the target Rails app root.
|
|
91
|
+
def detect_package_manager
|
|
92
|
+
# Check for lock files to determine package manager
|
|
93
|
+
return "yarn" if File.exist?("yarn.lock")
|
|
94
|
+
return "pnpm" if File.exist?("pnpm-lock.yaml")
|
|
95
|
+
return "bun" if File.exist?("bun.lock") || File.exist?("bun.lockb")
|
|
96
|
+
|
|
97
|
+
# Default to npm (Shakapacker 8.x default) - covers package-lock.json and no lockfile
|
|
98
|
+
"npm"
|
|
99
|
+
end
|
|
100
|
+
|
|
93
101
|
private
|
|
94
102
|
|
|
95
103
|
def build_render_example(component_name:, route:, rsc:)
|
|
@@ -100,6 +108,11 @@ module GeneratorMessages
|
|
|
100
108
|
end
|
|
101
109
|
end
|
|
102
110
|
|
|
111
|
+
def build_render_label(route:, rsc:)
|
|
112
|
+
prefix = rsc ? "Streaming server rendering" : "Server-side rendering - Enabled with prerender option"
|
|
113
|
+
"• #{prefix} in app/views/#{route}/index.html.erb:"
|
|
114
|
+
end
|
|
115
|
+
|
|
103
116
|
def build_process_manager_section
|
|
104
117
|
process_manager = detect_process_manager
|
|
105
118
|
if process_manager
|
|
@@ -196,17 +209,5 @@ module GeneratorMessages
|
|
|
196
209
|
# If version detection fails, don't show a warning to avoid noise
|
|
197
210
|
""
|
|
198
211
|
end
|
|
199
|
-
|
|
200
|
-
def detect_package_manager
|
|
201
|
-
# Check for lock files to determine package manager
|
|
202
|
-
if File.exist?("yarn.lock")
|
|
203
|
-
"yarn"
|
|
204
|
-
elsif File.exist?("pnpm-lock.yaml")
|
|
205
|
-
"pnpm"
|
|
206
|
-
else
|
|
207
|
-
# Default to npm (Shakapacker 8.x default) - covers package-lock.json and no lockfile
|
|
208
|
-
"npm"
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
212
|
end
|
|
212
213
|
end
|