react_on_rails 17.1.0 → 17.2.0.rc.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Gemfile.development_dependencies +2 -2
  4. data/Gemfile.lock +65 -73
  5. data/Steepfile +1 -0
  6. data/docs/agent/install-and-upgrade.md +8 -3
  7. data/docs/agent/rsc-adoption.md +3 -0
  8. data/lib/generators/react_on_rails/base_generator.rb +2 -1
  9. data/lib/generators/react_on_rails/generated_webpack_config_pair.rb +84 -0
  10. data/lib/generators/react_on_rails/generator_helper.rb +2 -4
  11. data/lib/generators/react_on_rails/generator_messages.rb +1 -1
  12. data/lib/generators/react_on_rails/install_generator.rb +2 -1
  13. data/lib/generators/react_on_rails/js_dependency_manager.rb +14 -14
  14. data/lib/generators/react_on_rails/pro/USAGE +14 -3
  15. data/lib/generators/react_on_rails/pro_generator.rb +1 -1
  16. data/lib/generators/react_on_rails/pro_setup.rb +41 -213
  17. data/lib/generators/react_on_rails/rsc_setup.rb +7 -16
  18. data/lib/generators/react_on_rails/templates/pro/base/config/initializers/react_on_rails_pro.rb.tt +1 -1
  19. data/lib/react_on_rails/agent_guardrails.rb +217 -39
  20. data/lib/react_on_rails/dev/process_manager.rb +14 -0
  21. data/lib/react_on_rails/dev/server_manager.rb +1727 -103
  22. data/lib/react_on_rails/doctor.rb +875 -44
  23. data/lib/react_on_rails/helper.rb +3 -3
  24. data/lib/react_on_rails/react_component/render_options.rb +5 -7
  25. data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +27 -116
  26. data/lib/react_on_rails/utils.rb +65 -0
  27. data/lib/react_on_rails/version.rb +1 -1
  28. data/lib/react_on_rails/version_checker/lockfile_resolution.rb +170 -0
  29. data/lib/react_on_rails/version_checker.rb +43 -22
  30. data/rakelib/run_rspec.rake +6 -1
  31. data/sig/react_on_rails/configuration.rbs +28 -26
  32. data/sig/react_on_rails/controller.rbs +2 -1
  33. data/sig/react_on_rails/dev/process_manager.rbs +1 -0
  34. data/sig/react_on_rails/dev/server_manager.rbs +150 -10
  35. data/sig/react_on_rails/git_utils.rbs +5 -1
  36. data/sig/react_on_rails/locales.rbs +4 -4
  37. data/sig/react_on_rails/prerender_error.rbs +2 -2
  38. data/sig/react_on_rails/test_helper.rbs +15 -1
  39. data/sig/react_on_rails/utils.rbs +2 -2
  40. data/sig/react_on_rails/version_checker/lockfile_resolution.rbs +31 -0
  41. data/sig/react_on_rails/version_checker.rbs +30 -7
  42. metadata +4 -1
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "securerandom"
4
4
  require_relative "generator_messages"
5
+ require_relative "generated_webpack_config_pair"
5
6
  require "react_on_rails/node_renderer_procfile"
6
7
  require "react_on_rails/pro_migration"
7
8
 
@@ -36,9 +37,8 @@ module ReactOnRails
36
37
  # Loader helpers emitted by
37
38
  # templates/base/base/config/webpack/serverWebpackConfig.js.tt.
38
39
  #
39
- # Keep both blocks byte-identical to that template: a fresh `--pro` install renders
40
- # the template while a standalone Pro upgrade patches an existing base config, and
41
- # the two must produce the same source text so drift is detectable (issue #4786).
40
+ # Keep these parity-test anchors byte-identical to the template (issue #4786).
41
+ # Migration recognition uses the entire rendered pair, never these snippets.
42
42
  GET_LOADER_PATH_JS = <<~JS
43
43
  // Normalizes an entry of a webpack/rspack `rule.use` array to its loader path.
44
44
  // Entries may be a bare string, a `{ loader, options }` object, or null.
@@ -56,15 +56,6 @@ module ReactOnRails
56
56
  }
57
57
  JS
58
58
 
59
- BUNDLER_REQUIRE_PATTERN =
60
- %r{(const bundler = config\.assets_bundler.*\n.*require\('@rspack/core'\).*\n.*: require\('webpack'\);)}
61
-
62
- # Matches any declaration of the getLoaderPath symbol, however it is written. The
63
- # emitted extractLoader calls getLoaderPath, so we must never add a second
64
- # declaration: `function` next to an existing `const` is a SyntaxError, not a
65
- # silent shadow, and the generated config would fail to parse in Node.
66
- GET_LOADER_PATH_DECLARATION = /(?:function\s+getLoaderPath\s*\(|(?:const|let|var)\s+getLoaderPath\s*=)/
67
-
68
59
  # Main entry point for Pro setup.
69
60
  # Orchestrates creation of all Pro-related files and configuration.
70
61
  #
@@ -157,8 +148,8 @@ module ReactOnRails
157
148
 
158
149
  Then run: bundle install
159
150
 
160
- No license needed for evaluation or non-production use.
161
- Free or low-cost production licenses available for startups and small companies.
151
+ No license key is needed to run Pro. Production is free for small organizations, charities, schools, and hospitals;
152
+ larger organizations subscribe: https://reactonrails.com/pricing/
162
153
  See the upgrade guide: https://reactonrails.com/docs/pro/upgrading-to-pro/
163
154
  MSG
164
155
  true
@@ -439,228 +430,65 @@ module ReactOnRails
439
430
  say "✅ Added Node Renderer to #{procfile}", :green
440
431
  end
441
432
 
442
- # Update webpack configs to enable Pro settings.
443
- # This is needed for standalone Pro upgrades where the base install
444
- # created webpack configs without Pro settings enabled.
445
- #
446
- # Updates serverWebpackConfig.js:
447
- # - Adds extractLoader helper function (required by rscWebpackConfig.js)
448
- # - Adds Babel SSR caller setup (required for correct SSR compilation)
449
- # - Enables libraryTarget: 'commonjs2' (required for Node Renderer)
450
- # - Enables serverWebpackConfig.target = 'node' (required for Node.js modules)
451
- # - Disables Node.js polyfills via node = false (required for real __dirname)
452
- # - Changes module.exports to Pro style (required by rscWebpackConfig.js)
453
- #
454
- # Updates ServerClientOrBoth.js:
455
- # - Changes import to destructured style (required for Pro object export)
433
+ # Only replace complete, unchanged generated pairs. Inspecting declarations or
434
+ # individual markers cannot establish that customized JavaScript is safe to rewrite.
456
435
  def update_webpack_config_for_pro
457
- webpack_config, webpack_config_path = webpack_config_paths
458
-
459
- unless File.exist?(webpack_config_path)
460
- say "ℹ️ serverWebpackConfig.js not found, skipping webpack update", :yellow
436
+ paths = generated_webpack_pair_paths
437
+ contents = read_generated_webpack_pair(paths) if paths
438
+ replacement = GeneratedWebpackConfigPair.pro_upgrade(contents) if contents
439
+ unless replacement
440
+ warn_unknown_webpack_pair
461
441
  return
462
442
  end
463
443
 
464
- content = File.read(webpack_config_path)
465
- server_config_ready = pro_server_config_ready?(content)
466
- import_ready = server_client_import_ready?
467
-
468
- # Skip only when both server config and import style are already updated.
469
- if server_config_ready && import_ready
444
+ if replacement == contents
470
445
  say "ℹ️ Webpack config already has Pro settings enabled, skipping", :yellow
471
446
  return
472
447
  end
473
448
 
474
- say "📝 Updating serverWebpackConfig.js for Pro...", :yellow
475
-
476
- unless server_config_ready
477
- # Add extractLoader helper function after bundler require
478
- add_extract_loader_to_server_config(webpack_config, content)
479
-
480
- # Add Babel SSR caller setup (uses extractLoader, so must come after)
481
- add_babel_ssr_caller_to_server_config(webpack_config, content)
482
-
483
- # Uncomment libraryTarget: 'commonjs2'
484
- library_target_pattern = %r{// If using the React on Rails Pro.*\n\s*// libraryTarget: 'commonjs2',}
485
- library_target_replacement = "// Required for React on Rails Pro Node Renderer\n " \
486
- "libraryTarget: 'commonjs2',"
487
- gsub_file(webpack_config, library_target_pattern, library_target_replacement)
488
-
489
- # Replace stale comments and uncomment target = 'node', add node = false
490
- # The base template has 4 lines: 2 explanatory comments + "uncomment" hint + commented code
491
- # Replace with clean Pro output matching the template's use_pro? branch
492
- # rubocop:disable Layout/LineLength
493
- target_node_pattern = %r{\s*// If using the default 'web',.*\n\s*// break with SSR\..*\n\s*// If using the React on Rails Pro.*\n\s*// serverWebpackConfig\.target = 'node'}
494
- # rubocop:enable Layout/LineLength
495
- target_node_replacement = "\n\n " \
496
- "// React on Rails Pro uses Node renderer, so target must be 'node'\n " \
497
- "// This fixes issues with libraries like Emotion and loadable-components\n " \
498
- "serverWebpackConfig.target = 'node';\n\n " \
499
- "// Disable Node.js polyfills - not needed when targeting Node\n " \
500
- "serverWebpackConfig.node = false;"
501
- gsub_file(webpack_config, target_node_pattern, target_node_replacement)
502
-
503
- # Change module.exports to Pro style (exports object with default and extractLoader)
504
- update_server_config_exports(webpack_config)
449
+ paths.zip(replacement).each do |path, content|
450
+ create_file(path, content, force: true)
505
451
  end
506
-
507
- # Update ServerClientOrBoth.js import style
508
- update_server_client_or_both_import
509
-
510
- verify_pro_webpack_transforms(webpack_config)
511
452
  say "✅ Updated webpack configs for Pro", :green
512
453
  end
513
454
 
514
- def webpack_config_paths
515
- config = destination_config_path("config/webpack/serverWebpackConfig.js")
516
- [config, File.join(destination_root, config)]
517
- end
518
-
519
- def add_extract_loader_to_server_config(webpack_config, content)
520
- # Skip if extractLoader already exists
521
- return if content.include?("function extractLoader")
522
-
523
- if content.include?(GET_LOADER_PATH_JS)
524
- # Config rendered by the current base template: append extractLoader directly after
525
- # the shared getLoaderPath helper so the result matches the template's Pro output.
526
- gsub_file(webpack_config, GET_LOADER_PATH_JS, "#{GET_LOADER_PATH_JS}\n#{EXTRACT_LOADER_JS}")
527
- elsif content.match?(GET_LOADER_PATH_DECLARATION)
528
- # The app already declares getLoaderPath but has customized it (reformatted, recommented,
529
- # or rewritten as an arrow function). Reuse whatever is there and emit extractLoader only.
530
- # Emitting our own copy would redeclare the identifier, which is a SyntaxError next to an
531
- # existing const/let and silent shadowing next to another function declaration.
532
- gsub_file(webpack_config, BUNDLER_REQUIRE_PATTERN, "\\1\n\n#{EXTRACT_LOADER_JS}".chomp)
533
- else
534
- # Base config generated before getLoaderPath existed: emit both helpers after the
535
- # bundler require so extractLoader's dependency is present.
536
- gsub_file(
537
- webpack_config,
538
- BUNDLER_REQUIRE_PATTERN,
539
- "\\1\n\n#{GET_LOADER_PATH_JS}\n#{EXTRACT_LOADER_JS}".chomp
540
- )
455
+ # Unlike resolve_server_client_or_both_path, discovery must not rename files
456
+ # or edit environment imports before the entire pair has been recognized.
457
+ def generated_webpack_pair_paths
458
+ server = destination_config_path("config/webpack/serverWebpackConfig.js")
459
+ companions = %w[ServerClientOrBoth.js generateWebpackConfigs.js].map do |name|
460
+ destination_config_path("config/webpack/#{name}")
541
461
  end
542
- end
462
+ existing = companions.select do |path|
463
+ absolute = File.join(destination_root, path)
464
+ File.exist?(absolute) || File.symlink?(absolute)
465
+ end
466
+ return unless existing.one?
543
467
 
544
- def add_babel_ssr_caller_to_server_config(webpack_config, content)
545
- return if content.include?("babelLoader.options.caller")
546
-
547
- babel_ssr_code = "\n\n " \
548
- "// Set SSR caller for Babel (if using Babel instead of SWC)\n " \
549
- "const babelLoader = extractLoader(rule, 'babel-loader');\n " \
550
- "if (babelLoader && babelLoader.options) {\n " \
551
- "babelLoader.options.caller = { ssr: true };\n " \
552
- "}"
553
-
554
- # Insert after cssLoader.options.modules; [\s\S]*? covers both single-line and spread syntax patterns.
555
- gsub_file(
556
- webpack_config,
557
- /(cssLoader\.options\.modules = \{[\s\S]*?exportOnlyLocals: true[\s\S]*?\};\s*\n\s*\})/,
558
- "\\1#{babel_ssr_code}"
559
- )
560
- new_content = File.read(File.join(destination_root, webpack_config))
561
- return if new_content.include?("babelLoader.options.caller")
468
+ paths = [server, existing.first]
469
+ return unless paths.all? do |path|
470
+ absolute = File.join(destination_root, path)
471
+ File.file?(absolute) && !File.symlink?(absolute)
472
+ end
562
473
 
563
- say_status :warning, "Babel SSR caller insertion failed in #{webpack_config}; manual edit required.", :yellow
474
+ paths
564
475
  end
565
476
 
566
- def update_server_config_exports(webpack_config)
567
- # Change from: module.exports = configureServer;
568
- # To: module.exports = { default: configureServer, extractLoader };
569
- gsub_file(
570
- webpack_config,
571
- /^module\.exports = configureServer;\s*$/,
572
- "module.exports = {\n default: configureServer,\n extractLoader,\n};\n"
573
- )
477
+ def read_generated_webpack_pair(paths)
478
+ paths.map { |path| File.binread(File.join(destination_root, path)) }
479
+ rescue SystemCallError, IOError
480
+ nil
574
481
  end
575
482
 
576
- def verify_pro_webpack_transforms(webpack_config)
577
- content = File.read(File.join(destination_root, webpack_config))
578
- missing = missing_server_config_transforms(content)
579
- missing.concat(missing_server_client_import_transform)
580
- return if missing.empty?
581
-
483
+ def warn_unknown_webpack_pair
582
484
  GeneratorMessages.add_warning(<<~MSG.strip)
583
- ⚠️ Some Pro webpack transforms may not have applied correctly.
584
-
585
- The following expected patterns were not found in #{webpack_config}:
586
- #{missing.map { |m| " - #{m}" }.join("\n")}
587
-
588
- This can happen if your webpack config has been customized.
589
- Please verify #{webpack_config} manually.
485
+ Skipped Pro webpack updates: the config pair is missing, ambiguous, customized,
486
+ or does not match a supported current generated pair.
487
+ Both webpack configs and their paths were left unchanged. Update them manually using the Pro setup guide:
488
+ https://reactonrails.com/docs/pro/upgrading-to-pro/
590
489
  MSG
591
490
  end
592
491
 
593
- def missing_server_config_transforms(content)
594
- checks = [
595
- "libraryTarget: 'commonjs2',",
596
- "function extractLoader",
597
- "babelLoader.options.caller = { ssr: true }",
598
- "serverWebpackConfig.target = 'node'",
599
- "serverWebpackConfig.node = false",
600
- "default: configureServer",
601
- "extractLoader,"
602
- ]
603
-
604
- checks.reject { |pattern| content.include?(pattern) }
605
- end
606
-
607
- def missing_server_client_import_transform
608
- server_client_path = resolve_server_client_or_both_path
609
- return [] unless server_client_path
610
-
611
- content = File.read(File.join(destination_root, server_client_path))
612
- return [] if content.include?("{ default: serverWebpackConfig }")
613
-
614
- ["{ default: serverWebpackConfig }"]
615
- end
616
-
617
- def update_server_client_or_both_import
618
- server_client_path = resolve_server_client_or_both_path
619
- return unless server_client_path
620
-
621
- content = File.read(File.join(destination_root, server_client_path))
622
-
623
- # Skip if already using destructured import
624
- return if content.include?("{ default: serverWebpackConfig }")
625
-
626
- # Change from: const serverWebpackConfig = require('./serverWebpackConfig');
627
- # To: const { default: serverWebpackConfig } = require('./serverWebpackConfig');
628
- gsub_file(
629
- server_client_path,
630
- %r{^const serverWebpackConfig = require\('\./serverWebpackConfig'\);$},
631
- "const { default: serverWebpackConfig } = require('./serverWebpackConfig');"
632
- )
633
-
634
- new_content = File.read(File.join(destination_root, server_client_path))
635
- return if new_content.include?("{ default: serverWebpackConfig }")
636
-
637
- say_status(
638
- :warning,
639
- "ServerClientOrBoth import update failed in #{server_client_path}; manual edit required.",
640
- :yellow
641
- )
642
- end
643
-
644
- def pro_server_config_ready?(content)
645
- # Check for the Pro-specific comment marker (written by the transform) to avoid
646
- # false-negatives when commented-out lines also contain the pattern string.
647
- content.include?("// Required for React on Rails Pro Node Renderer") &&
648
- content.include?("function extractLoader") &&
649
- content.include?("babelLoader.options.caller = { ssr: true }") &&
650
- content.include?("serverWebpackConfig.target = 'node'") &&
651
- content.include?("serverWebpackConfig.node = false") &&
652
- content.include?("default: configureServer") &&
653
- content.include?("extractLoader,")
654
- end
655
-
656
- def server_client_import_ready?
657
- server_client_path = resolve_server_client_or_both_path
658
- return true unless server_client_path
659
-
660
- content = File.read(File.join(destination_root, server_client_path))
661
- content.include?("{ default: serverWebpackConfig }")
662
- end
663
-
664
492
  def pro_gem_auto_install_command
665
493
  "bundle add #{PRO_GEM_NAME} --version='#{pro_gem_version_requirement}' --strict"
666
494
  end
@@ -82,17 +82,9 @@ module ReactOnRails
82
82
  react_version = detect_react_version
83
83
  return if react_version.nil? # React not installed yet, will be installed by generator
84
84
 
85
- version = Gem::Version.new(react_version)
86
- major, minor, patch = version.segments
85
+ major, minor, patch = react_version.split(".").map(&:to_i)
87
86
 
88
- if version.prerelease?
89
- GeneratorMessages.add_warning(<<~MSG.strip)
90
- ⚠️ RSC requires stable React (detected: #{react_version}).
91
-
92
- React prereleases are not supported. Install matching stable React/React DOM versions:
93
- #{manual_add_packages_command(["react@#{RSC_REACT_VERSION_RANGE}", "react-dom@#{RSC_REACT_VERSION_RANGE}"])}
94
- MSG
95
- elsif major != RSC_SUPPORTED_REACT_MAJOR || minor != RSC_SUPPORTED_REACT_MINOR
87
+ if major != RSC_SUPPORTED_REACT_MAJOR || minor != RSC_SUPPORTED_REACT_MINOR
96
88
  GeneratorMessages.add_warning(<<~MSG.strip)
97
89
  ⚠️ RSC requires React #{RSC_SUPPORTED_REACT_LINE} (detected: #{react_version})
98
90
 
@@ -105,14 +97,13 @@ module ReactOnRails
105
97
  MSG
106
98
  elsif patch < RSC_MINIMUM_REACT_PATCH
107
99
  GeneratorMessages.add_warning(<<~MSG.strip)
108
- ⚠️ React #{react_version} is below the required minimum for RSC.
100
+ ⚠️ React #{react_version} is below the recommended minimum for RSC.
109
101
 
110
- react-on-rails-rsc #{JsDependencyManager::RSC_PACKAGE_VERSION_PIN} requires
111
- matching stable React/React DOM #{RSC_MINIMUM_REACT_VERSION}+ on #{RSC_SUPPORTED_REACT_LINE}.
112
- The Node Renderer refuses startup until React and React DOM are upgraded together:
113
- #{manual_add_packages_command(["react@#{RSC_REACT_VERSION_RANGE}", "react-dom@#{RSC_REACT_VERSION_RANGE}"])}
102
+ Please upgrade to at least React #{RSC_MINIMUM_REACT_VERSION}:
103
+ #{manual_add_packages_command(["react@#{RSC_MINIMUM_REACT_VERSION}", "react-dom@#{RSC_MINIMUM_REACT_VERSION}"])}
114
104
 
115
- The standalone RSC generator does not upgrade your React or React DOM dependencies.
105
+ react-on-rails-rsc 19.2.x with patch >= 19.2.1 is coordinated with
106
+ React/React DOM #{RSC_MINIMUM_REACT_VERSION}+ for the React on Rails Pro 17 RSC runtime.
116
107
  MSG
117
108
  end
118
109
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # See https://reactonrails.com/docs/configuration/configuration-pro
4
- # License: Set REACT_ON_RAILS_PRO_LICENSE environment variable
4
+ # Optional license key from your subscription: set REACT_ON_RAILS_PRO_LICENSE to mark your pages Licensed.
5
5
  ReactOnRailsPro.configure do |config|
6
6
  config.server_renderer = "NodeRenderer"
7
7
  config.renderer_url = ENV.fetch("REACT_RENDERER_URL", "http://localhost:3800")