react_on_rails 16.2.0.beta.3 → 16.2.0.beta.8

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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -5
  3. data/CLAUDE.md +59 -0
  4. data/CONTRIBUTING.md +49 -1
  5. data/Gemfile.development_dependencies +1 -1
  6. data/Gemfile.lock +25 -10
  7. data/SWITCHING_CI_CONFIGS.md +55 -6
  8. data/Steepfile +51 -0
  9. data/bin/ci-rerun-failures +68 -22
  10. data/bin/ci-run-failed-specs +26 -2
  11. data/bin/ci-switch-config +262 -34
  12. data/bin/lefthook/check-trailing-newlines +2 -12
  13. data/bin/lefthook/eslint-lint +0 -10
  14. data/bin/lefthook/prettier-format +0 -10
  15. data/bin/lefthook/ruby-autofix +3 -6
  16. data/knip.ts +35 -9
  17. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +32 -52
  18. data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +5 -1
  19. data/lib/react_on_rails/configuration.rb +56 -12
  20. data/lib/react_on_rails/controller.rb +3 -3
  21. data/lib/react_on_rails/dev/server_manager.rb +11 -4
  22. data/lib/react_on_rails/doctor.rb +249 -2
  23. data/lib/react_on_rails/helper.rb +12 -3
  24. data/lib/react_on_rails/pro_helper.rb +2 -44
  25. data/lib/react_on_rails/react_component/render_options.rb +7 -7
  26. data/lib/react_on_rails/utils.rb +40 -0
  27. data/lib/react_on_rails/version.rb +1 -1
  28. data/react_on_rails_pro/CHANGELOG.md +142 -29
  29. data/react_on_rails_pro/CONTRIBUTING.md +2 -13
  30. data/react_on_rails_pro/Gemfile.development_dependencies +1 -0
  31. data/react_on_rails_pro/Gemfile.lock +24 -3
  32. data/react_on_rails_pro/README.md +559 -38
  33. data/react_on_rails_pro/docs/code-splitting-loadable-components.md +1 -1
  34. data/react_on_rails_pro/docs/contributors-info/releasing.md +2 -2
  35. data/react_on_rails_pro/docs/installation.md +129 -109
  36. data/react_on_rails_pro/docs/node-renderer/basics.md +29 -22
  37. data/react_on_rails_pro/docs/node-renderer/error-reporting-and-tracing.md +8 -8
  38. data/react_on_rails_pro/docs/node-renderer/js-configuration.md +25 -23
  39. data/react_on_rails_pro/docs/node-renderer/troubleshooting.md +2 -0
  40. data/react_on_rails_pro/docs/updating.md +209 -15
  41. data/react_on_rails_pro/lib/react_on_rails_pro/concerns/stream.rb +58 -4
  42. data/react_on_rails_pro/lib/react_on_rails_pro/configuration.rb +17 -3
  43. data/react_on_rails_pro/lib/react_on_rails_pro/license_public_key.rb +9 -9
  44. data/react_on_rails_pro/lib/react_on_rails_pro/request.rb +41 -25
  45. data/react_on_rails_pro/lib/react_on_rails_pro/stream_request.rb +27 -7
  46. data/react_on_rails_pro/lib/react_on_rails_pro/utils.rb +3 -3
  47. data/react_on_rails_pro/lib/react_on_rails_pro/version.rb +1 -1
  48. data/react_on_rails_pro/package-scripts.yml +1 -1
  49. data/react_on_rails_pro/package.json +5 -8
  50. data/react_on_rails_pro/packages/node-renderer/src/integrations/api.ts +1 -1
  51. data/react_on_rails_pro/packages/node-renderer/src/master/restartWorkers.ts +39 -17
  52. data/react_on_rails_pro/packages/node-renderer/src/master.ts +15 -4
  53. data/react_on_rails_pro/packages/node-renderer/src/shared/configBuilder.ts +44 -5
  54. data/react_on_rails_pro/packages/node-renderer/src/shared/utils.ts +4 -2
  55. data/react_on_rails_pro/packages/node-renderer/src/worker/handleGracefulShutdown.ts +49 -0
  56. data/react_on_rails_pro/packages/node-renderer/src/worker/vm.ts +3 -3
  57. data/react_on_rails_pro/packages/node-renderer/src/worker.ts +5 -2
  58. data/react_on_rails_pro/packages/node-renderer/tests/helper.ts +8 -8
  59. data/react_on_rails_pro/packages/node-renderer/tests/testingNodeRendererConfigs.js +1 -1
  60. data/react_on_rails_pro/packages/node-renderer/tests/worker.test.ts +19 -19
  61. data/react_on_rails_pro/rakelib/public_key_management.rake +6 -5
  62. data/react_on_rails_pro/rakelib/rbs.rake +47 -0
  63. data/react_on_rails_pro/react_on_rails_pro.gemspec +1 -0
  64. data/react_on_rails_pro/sig/react_on_rails_pro/cache.rbs +13 -0
  65. data/react_on_rails_pro/sig/react_on_rails_pro/configuration.rbs +100 -0
  66. data/react_on_rails_pro/sig/react_on_rails_pro/error.rbs +4 -0
  67. data/react_on_rails_pro/sig/react_on_rails_pro/utils.rbs +7 -0
  68. data/react_on_rails_pro/sig/react_on_rails_pro.rbs +5 -0
  69. data/react_on_rails_pro/spec/dummy/Gemfile.lock +23 -3
  70. data/react_on_rails_pro/spec/dummy/app/controllers/pages_controller.rb +3 -3
  71. data/react_on_rails_pro/spec/dummy/bin/dev +4 -8
  72. data/react_on_rails_pro/spec/dummy/client/node-renderer.js +4 -4
  73. data/react_on_rails_pro/spec/dummy/config/environments/production.rb +1 -1
  74. data/react_on_rails_pro/spec/dummy/config/initializers/react_on_rails.rb +28 -12
  75. data/react_on_rails_pro/spec/dummy/config.ru +1 -1
  76. data/react_on_rails_pro/spec/dummy/package.json +2 -2
  77. data/react_on_rails_pro/spec/dummy/spec/helpers/react_on_rails_pro_helper_spec.rb +40 -11
  78. data/react_on_rails_pro/spec/dummy/spec/rails_helper.rb +1 -1
  79. data/react_on_rails_pro/spec/dummy/spec/requests/renderer_console_logging_spec.rb +5 -5
  80. data/react_on_rails_pro/spec/dummy/spec/system/integration_spec.rb +15 -10
  81. data/react_on_rails_pro/spec/dummy/spec/system/renderer_integration_spec.rb +3 -3
  82. data/react_on_rails_pro/spec/dummy/yarn.lock +4 -4
  83. data/react_on_rails_pro/spec/execjs-compatible-dummy/config/environments/production.rb +1 -1
  84. data/react_on_rails_pro/spec/execjs-compatible-dummy/config/initializers/react_on_rails.rb +16 -43
  85. data/react_on_rails_pro/spec/react_on_rails_pro/assets_precompile_spec.rb +15 -18
  86. data/react_on_rails_pro/spec/react_on_rails_pro/cache_spec.rb +1 -1
  87. data/react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb +5 -3
  88. data/react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb +27 -12
  89. data/react_on_rails_pro/spec/react_on_rails_pro/request_spec.rb +0 -27
  90. data/react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb +1 -1
  91. data/react_on_rails_pro/spec/react_on_rails_pro/stream_decorator_spec.rb +89 -0
  92. data/react_on_rails_pro/spec/react_on_rails_pro/stream_spec.rb +144 -0
  93. data/react_on_rails_pro/spec/react_on_rails_pro/support/caching.rb +1 -1
  94. data/react_on_rails_pro/spec/react_on_rails_pro/support/mock_block_helper.rb +4 -2
  95. data/sig/react_on_rails/controller.rbs +1 -1
  96. data/sig/react_on_rails/error.rbs +4 -0
  97. data/sig/react_on_rails/helper.rbs +2 -2
  98. data/sig/react_on_rails/json_parse_error.rbs +10 -0
  99. data/sig/react_on_rails/prerender_error.rbs +21 -0
  100. data/sig/react_on_rails/smart_error.rbs +28 -0
  101. data/sig/react_on_rails.rbs +3 -24
  102. metadata +14 -4
  103. data/lib/react_on_rails/pro_utils.rb +0 -37
  104. data/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/TestingStreamableComponent.jsx +0 -15
@@ -37,6 +37,7 @@ Gem::Specification.new do |s|
37
37
  s.add_runtime_dependency "execjs", "~> 2.9"
38
38
  s.add_runtime_dependency "httpx", "~> 1.5"
39
39
  s.add_runtime_dependency "jwt", "~> 2.7"
40
+ s.add_runtime_dependency "async", ">= 2.6"
40
41
  s.add_runtime_dependency "rainbow"
41
42
  s.add_runtime_dependency "react_on_rails", ReactOnRails::VERSION
42
43
  s.add_development_dependency "bundler"
@@ -0,0 +1,13 @@
1
+ module ReactOnRailsPro
2
+ class Cache
3
+ def self.fetch_react_component: (String component_name, Hash[Symbol, untyped] options) { () -> untyped } -> untyped
4
+
5
+ def self.use_cache?: (Hash[Symbol, untyped] options) -> bool
6
+
7
+ def self.base_cache_key: (String type, ?prerender: bool?) -> Array[String]
8
+
9
+ def self.dependencies_cache_key: () -> String?
10
+
11
+ def self.react_component_cache_key: (String component_name, Hash[Symbol, untyped] options) -> Array[untyped]
12
+ end
13
+ end
@@ -0,0 +1,100 @@
1
+ module ReactOnRailsPro
2
+ class Configuration
3
+ DEFAULT_RENDERER_URL: String
4
+ DEFAULT_RENDERER_METHOD: String
5
+ DEFAULT_RENDERER_FALLBACK_EXEC_JS: bool
6
+ DEFAULT_RENDERER_HTTP_POOL_SIZE: Integer
7
+ DEFAULT_RENDERER_HTTP_POOL_TIMEOUT: Integer
8
+ DEFAULT_RENDERER_HTTP_POOL_WARN_TIMEOUT: Float
9
+ DEFAULT_SSR_TIMEOUT: Integer
10
+ DEFAULT_PRERENDER_CACHING: bool
11
+ DEFAULT_TRACING: bool
12
+ DEFAULT_DEPENDENCY_GLOBS: Array[String]
13
+ DEFAULT_EXCLUDED_DEPENDENCY_GLOBS: Array[String]
14
+ DEFAULT_REMOTE_BUNDLE_CACHE_ADAPTER: nil
15
+ DEFAULT_RENDERER_REQUEST_RETRY_LIMIT: Integer
16
+ DEFAULT_THROW_JS_ERRORS: bool
17
+ DEFAULT_RENDERING_RETURNS_PROMISES: bool
18
+ DEFAULT_PROFILE_SERVER_RENDERING_JS_CODE: bool
19
+ DEFAULT_RAISE_NON_SHELL_SERVER_RENDERING_ERRORS: bool
20
+ DEFAULT_ENABLE_RSC_SUPPORT: bool
21
+ DEFAULT_RSC_PAYLOAD_GENERATION_URL_PATH: String
22
+ DEFAULT_RSC_BUNDLE_JS_FILE: String
23
+ DEFAULT_REACT_CLIENT_MANIFEST_FILE: String
24
+ DEFAULT_REACT_SERVER_CLIENT_MANIFEST_FILE: String
25
+
26
+ attr_accessor renderer_url: String?
27
+ attr_accessor renderer_password: String?
28
+ attr_accessor tracing: bool?
29
+ attr_accessor server_renderer: String?
30
+ attr_accessor renderer_use_fallback_exec_js: bool?
31
+ attr_accessor prerender_caching: bool?
32
+ attr_accessor renderer_http_pool_size: Integer?
33
+ attr_accessor renderer_http_pool_timeout: Integer?
34
+ attr_accessor renderer_http_pool_warn_timeout: Float?
35
+ attr_accessor dependency_globs: Array[String]?
36
+ attr_accessor excluded_dependency_globs: Array[String]?
37
+ attr_accessor rendering_returns_promises: bool?
38
+ attr_accessor remote_bundle_cache_adapter: Module?
39
+ attr_accessor ssr_pre_hook_js: String?
40
+ attr_accessor assets_to_copy: Array[String]?
41
+ attr_accessor renderer_request_retry_limit: Integer?
42
+ attr_accessor throw_js_errors: bool?
43
+ attr_accessor ssr_timeout: Integer?
44
+ attr_accessor profile_server_rendering_js_code: bool?
45
+ attr_accessor raise_non_shell_server_rendering_errors: bool?
46
+ attr_accessor enable_rsc_support: bool?
47
+ attr_accessor rsc_payload_generation_url_path: String?
48
+ attr_accessor rsc_bundle_js_file: String?
49
+ attr_accessor react_client_manifest_file: String?
50
+ attr_accessor react_server_client_manifest_file: String?
51
+
52
+ def initialize: (
53
+ ?renderer_url: String?,
54
+ ?renderer_password: String?,
55
+ ?server_renderer: String?,
56
+ ?renderer_use_fallback_exec_js: bool?,
57
+ ?prerender_caching: bool?,
58
+ ?renderer_http_pool_size: Integer?,
59
+ ?renderer_http_pool_timeout: Integer?,
60
+ ?renderer_http_pool_warn_timeout: Float?,
61
+ ?tracing: bool?,
62
+ ?dependency_globs: Array[String]?,
63
+ ?excluded_dependency_globs: Array[String]?,
64
+ ?rendering_returns_promises: bool?,
65
+ ?remote_bundle_cache_adapter: Module?,
66
+ ?ssr_pre_hook_js: String?,
67
+ ?assets_to_copy: Array[String]?,
68
+ ?renderer_request_retry_limit: Integer?,
69
+ ?throw_js_errors: bool?,
70
+ ?ssr_timeout: Integer?,
71
+ ?profile_server_rendering_js_code: bool?,
72
+ ?raise_non_shell_server_rendering_errors: bool?,
73
+ ?enable_rsc_support: bool?,
74
+ ?rsc_payload_generation_url_path: String?,
75
+ ?rsc_bundle_js_file: String?,
76
+ ?react_client_manifest_file: String?,
77
+ ?react_server_client_manifest_file: String?
78
+ ) -> void
79
+
80
+ def setup_config_values: () -> void
81
+
82
+ def check_react_on_rails_support_for_rsc: () -> void
83
+
84
+ def setup_execjs_profiler_if_needed: () -> void
85
+
86
+ def node_renderer?: () -> bool
87
+
88
+ private
89
+
90
+ def setup_assets_to_copy: () -> void
91
+
92
+ def configure_default_url_if_not_provided: () -> void
93
+
94
+ def validate_url: () -> void
95
+
96
+ def validate_remote_bundle_cache_adapter: () -> void
97
+
98
+ def setup_renderer_password: () -> void
99
+ end
100
+ end
@@ -0,0 +1,4 @@
1
+ module ReactOnRailsPro
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,7 @@
1
+ module ReactOnRailsPro
2
+ module Utils
3
+ def self.bundle_js_file_path: (String bundle_name) -> String
4
+
5
+ def self.running_on_windows?: () -> bool
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module ReactOnRailsPro
2
+ def self.configure: () { (Configuration) -> void } -> void
3
+
4
+ def self.configuration: () -> Configuration
5
+ end
@@ -9,7 +9,7 @@ GIT
9
9
  PATH
10
10
  remote: ../../..
11
11
  specs:
12
- react_on_rails (16.2.0.beta.3)
12
+ react_on_rails (16.2.0.beta.8)
13
13
  addressable
14
14
  connection_pool
15
15
  execjs (~> 2.5)
@@ -20,14 +20,15 @@ PATH
20
20
  PATH
21
21
  remote: ../..
22
22
  specs:
23
- react_on_rails_pro (16.2.0.beta.3)
23
+ react_on_rails_pro (16.2.0.beta.8)
24
24
  addressable
25
+ async (>= 2.6)
25
26
  connection_pool
26
27
  execjs (~> 2.9)
27
28
  httpx (~> 1.5)
28
29
  jwt (~> 2.7)
29
30
  rainbow
30
- react_on_rails (= 16.2.0.beta.3)
31
+ react_on_rails (= 16.2.0.beta.8)
31
32
 
32
33
  GEM
33
34
  remote: https://rubygems.org/
@@ -107,6 +108,12 @@ GEM
107
108
  public_suffix (>= 2.0.2, < 7.0)
108
109
  amazing_print (1.6.0)
109
110
  ast (2.4.2)
111
+ async (2.34.0)
112
+ console (~> 1.29)
113
+ fiber-annotation
114
+ io-event (~> 1.11)
115
+ metrics (~> 0.12)
116
+ traces (~> 0.18)
110
117
  base64 (0.2.0)
111
118
  benchmark (0.4.0)
112
119
  bigdecimal (3.1.9)
@@ -131,6 +138,10 @@ GEM
131
138
  coderay (1.1.3)
132
139
  concurrent-ruby (1.3.5)
133
140
  connection_pool (2.5.0)
141
+ console (1.34.2)
142
+ fiber-annotation
143
+ fiber-local (~> 1.1)
144
+ json
134
145
  coveralls (0.8.23)
135
146
  json (>= 1.8, < 3)
136
147
  simplecov (~> 0.16.1)
@@ -165,6 +176,9 @@ GEM
165
176
  ffi (1.17.0-x86_64-darwin)
166
177
  ffi (1.17.0-x86_64-linux-gnu)
167
178
  ffi (1.17.0-x86_64-linux-musl)
179
+ fiber-annotation (0.2.0)
180
+ fiber-local (1.1.0)
181
+ fiber-storage
168
182
  fiber-storage (1.0.0)
169
183
  generator_spec (0.10.0)
170
184
  activesupport (>= 3.0.0)
@@ -184,6 +198,7 @@ GEM
184
198
  i18n (1.14.7)
185
199
  concurrent-ruby (~> 1.0)
186
200
  io-console (0.8.0)
201
+ io-event (1.14.2)
187
202
  irb (1.15.1)
188
203
  pp (>= 0.6.0)
189
204
  rdoc (>= 4.0.0)
@@ -216,6 +231,7 @@ GEM
216
231
  marcel (1.0.4)
217
232
  matrix (0.4.2)
218
233
  method_source (1.1.0)
234
+ metrics (0.15.0)
219
235
  mini_mime (1.1.5)
220
236
  mini_portile2 (2.8.8)
221
237
  minitest (5.25.4)
@@ -327,6 +343,8 @@ GEM
327
343
  rb-fsevent (0.11.2)
328
344
  rb-inotify (0.11.1)
329
345
  ffi (~> 1.0)
346
+ rbs (3.9.5)
347
+ logger
330
348
  rdoc (6.12.0)
331
349
  psych (>= 4.0.0)
332
350
  redis (5.4.0)
@@ -445,6 +463,7 @@ GEM
445
463
  tins (1.33.0)
446
464
  bigdecimal
447
465
  sync
466
+ traces (0.18.2)
448
467
  turbolinks (5.2.1)
449
468
  turbolinks-source (~> 5.2)
450
469
  turbolinks-source (5.2.0)
@@ -524,6 +543,7 @@ DEPENDENCIES
524
543
  pry-theme
525
544
  puma (~> 6)
526
545
  rails (~> 7.1)
546
+ rbs
527
547
  react_on_rails!
528
548
  react_on_rails_pro!
529
549
  redis
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class PagesController < ApplicationController
3
+ class PagesController < ApplicationController # rubocop:disable Metrics/ClassLength
4
4
  include ReactOnRailsPro::RSCPayloadRenderer
5
5
  include RscPostsPageOverRedisHelper
6
6
 
@@ -85,8 +85,8 @@ class PagesController < ApplicationController
85
85
  ensure
86
86
  begin
87
87
  redis&.close
88
- rescue StandardError => close_err
89
- Rails.logger.warn "Failed to close Redis: #{close_err.message}"
88
+ rescue StandardError => e
89
+ Rails.logger.warn "Failed to close Redis: #{e.message}"
90
90
  end
91
91
  end
92
92
 
@@ -1,9 +1,5 @@
1
- #!/usr/bin/env bash
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- if ! command -v foreman &> /dev/null
4
- then
5
- echo "Installing foreman..."
6
- gem install foreman
7
- fi
8
-
9
- foreman start -f Procfile.dev
4
+ # This script calls the base dev script with a fixed route for the dummy app
5
+ exec File.join(__dir__, "../../../../lib/generators/react_on_rails/templates/base/base/bin/dev"), "--route=/", *ARGV
@@ -5,14 +5,14 @@ const Sentry = require('@sentry/node');
5
5
  const { env } = process;
6
6
 
7
7
  // Use this for package installation test:
8
- const { reactOnRailsProNodeRenderer } = require('@shakacode-tools/react-on-rails-pro-node-renderer');
8
+ const { reactOnRailsProNodeRenderer } = require('react-on-rails-pro-node-renderer');
9
9
 
10
10
  Honeybadger.configure({
11
11
  // This is a test account for React on Rails Pro. Substitute your own.
12
12
  apiKey: 'a602365c',
13
13
  environment: process.env.NODE_ENV ?? 'development',
14
14
  });
15
- require('@shakacode-tools/react-on-rails-pro-node-renderer/integrations/honeybadger').init();
15
+ require('react-on-rails-pro-node-renderer/integrations/honeybadger').init();
16
16
 
17
17
  // This is a test account for React on Rails Pro.
18
18
  // Substitute your own DSN.
@@ -25,11 +25,11 @@ Sentry.init({
25
25
  // Sentry recommends adjusting this value in production, or using tracesSampler for finer control
26
26
  tracesSampleRate: 1.0,
27
27
  });
28
- require('@shakacode-tools/react-on-rails-pro-node-renderer/integrations/sentry').init({ tracing: true });
28
+ require('react-on-rails-pro-node-renderer/integrations/sentry').init({ tracing: true });
29
29
 
30
30
  const config = {
31
31
  // This is the default but avoids searching for the Rails root
32
- bundlePath: path.resolve(__dirname, '../.node-renderer-bundles'),
32
+ serverBundleCachePath: path.resolve(__dirname, '../.node-renderer-bundles'),
33
33
  port: env.RENDERER_PORT || 3800, // Listen at RENDERER_PORT env value or default port 3800
34
34
  logLevel: env.RENDERER_LOG_LEVEL || 'info',
35
35
 
@@ -69,7 +69,7 @@ Rails.application.configure do
69
69
  config.active_support.deprecation = :notify
70
70
 
71
71
  # Use default logging formatter so that PID and timestamp are not suppressed.
72
- config.log_formatter = ::Logger::Formatter.new
72
+ config.log_formatter = Logger::Formatter.new
73
73
 
74
74
  # Use a different logger for distributed setups.
75
75
  # require 'syslog/logger'
@@ -1,9 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # For documentation of parameters see: docs/basics/configuration.md
3
+ # ⚠️ TEST CONFIGURATION - Do not copy directly for production apps
4
+ # This is the Pro dummy app configuration used for testing React on Rails Pro features.
5
+ # See docs/api-reference/configuration.md for production configuration guidance.
6
+
7
+ # Advanced: Custom rendering extension to add values to railsContext
4
8
  module RenderingExtension
5
- # Return a Hash that contains custom values from the view context that will get passed to
6
- # all calls to react_component and redux_store for rendering
7
9
  def self.custom_context(view_context)
8
10
  if view_context.controller.is_a?(ActionMailer::Base)
9
11
  {}
@@ -15,6 +17,7 @@ module RenderingExtension
15
17
  end
16
18
  end
17
19
 
20
+ # Advanced: Custom props extension for client-side hydration
18
21
  module RenderingPropsExtension
19
22
  def self.adjust_props_for_client_side_hydration(_component_name, props)
20
23
  if props.instance_of?(Hash)
@@ -26,19 +29,32 @@ module RenderingPropsExtension
26
29
  end
27
30
 
28
31
  ReactOnRails.configure do |config|
32
+ ################################################################################
33
+ # Essential Configuration
34
+ ################################################################################
29
35
  config.server_bundle_js_file = "server-bundle.js"
36
+ config.components_subdirectory = "ror-auto-load-components"
37
+ config.auto_load_bundle = true
38
+
39
+ ################################################################################
40
+ # Pro Feature Testing: Server Bundle Security
41
+ ################################################################################
42
+ # Testing private server bundle enforcement (recommended for production)
43
+ config.enforce_private_server_bundles = true
44
+ config.server_bundle_output_path = "ssr-generated"
45
+
46
+ ################################################################################
47
+ # Test-specific Advanced Configuration
48
+ ################################################################################
49
+ # Testing with fixed DOM IDs for easier test assertions
30
50
  config.random_dom_id = false # default is true
31
51
 
32
- # Next 2 lines are commented out because we've set test.compile to true
33
- # config.build_test_command = "yarn run build:test"
34
- # config.webpack_generated_files = %w[server-bundle.js manifest.json]
52
+ # Testing advanced rendering customization
35
53
  config.rendering_extension = RenderingExtension
36
-
37
54
  config.rendering_props_extension = RenderingPropsExtension
38
55
 
39
- config.auto_load_bundle = true
40
- config.components_subdirectory = "ror-auto-load-components"
41
-
42
- config.enforce_private_server_bundles = true
43
- config.server_bundle_output_path = "ssr-generated"
56
+ # NOTE: build_test_command and webpack_generated_files are commented out
57
+ # because we've set test.compile to true in shakapacker.yml
58
+ # config.build_test_command = "yarn run build:test"
59
+ # config.webpack_generated_files = %w[server-bundle.js manifest.json]
44
60
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  # This file is used by Rack-based servers to start the application.
4
4
 
5
- require ::File.expand_path("config/environment", __dir__)
5
+ require File.expand_path("config/environment", __dir__)
6
6
 
7
7
  run Rails.application
@@ -17,7 +17,7 @@
17
17
  "@loadable/server": "^5.16.2",
18
18
  "@loadable/webpack-plugin": "^5.15.2",
19
19
  "@sentry/node": "^7.120.0",
20
- "@shakacode-tools/react-on-rails-pro-node-renderer": "link:.yalc/@shakacode-tools/react-on-rails-pro-node-renderer",
20
+ "react-on-rails-pro-node-renderer": "link:.yalc/react-on-rails-pro-node-renderer",
21
21
  "@shakacode/use-ssr-computation.macro": "^1.2.4",
22
22
  "@shakacode/use-ssr-computation.runtime": "^2.0.0",
23
23
  "@webpack-cli/serve": "^1.6.0",
@@ -98,7 +98,7 @@
98
98
  "test": "yarn run build:test && yarn run lint && rspec",
99
99
  "lint": "cd ../.. && nps lint",
100
100
  "e2e-test": "playwright test",
101
- "preinstall": "yarn run link-source && yalc add --link react-on-rails-pro && cd .yalc/react-on-rails-pro && yalc add --link react-on-rails && cd ../.. && yalc add --link @shakacode-tools/react-on-rails-pro-node-renderer",
101
+ "preinstall": "yarn run link-source && yalc add --link react-on-rails-pro && cd .yalc/react-on-rails-pro && yalc add --link react-on-rails && cd ../.. && yalc add --link react-on-rails-pro-node-renderer",
102
102
  "link-source": "cd ../../.. && yarn && yarn run yalc:publish && cd react_on_rails_pro && yarn && yalc publish",
103
103
  "postinstall": "test -f post-yarn-install.local && ./post-yarn-install.local || true",
104
104
  "build:test": "rm -rf public/webpack/test && rm -rf ssr-generated && RAILS_ENV=test NODE_ENV=test bin/shakapacker",
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "async"
4
+ require "async/queue"
3
5
  require "rails_helper"
4
6
  require "support/script_tag_utils"
5
7
 
@@ -327,10 +329,10 @@ describe ReactOnRailsProHelper do
327
329
  HTML
328
330
  end
329
331
 
332
+ # mock_chunks can be an Async::Queue or an Array
330
333
  def mock_request_and_response(mock_chunks = chunks, count: 1)
331
334
  # Reset connection instance variables to ensure clean state for tests
332
335
  ReactOnRailsPro::Request.instance_variable_set(:@connection, nil)
333
- ReactOnRailsPro::Request.instance_variable_set(:@connection_without_retries, nil)
334
336
  original_httpx_plugin = HTTPX.method(:plugin)
335
337
  allow(HTTPX).to receive(:plugin) do |*args|
336
338
  original_httpx_plugin.call(:mock_stream).plugin(*args)
@@ -340,9 +342,19 @@ describe ReactOnRailsProHelper do
340
342
  chunks_read.clear
341
343
  mock_streaming_response(%r{http://localhost:3800/bundles/[a-f0-9]{32}-test/render/[a-f0-9]{32}}, 200,
342
344
  count: count) do |yielder|
343
- mock_chunks.each do |chunk|
344
- chunks_read << chunk
345
- yielder.call("#{chunk.to_json}\n")
345
+ if mock_chunks.is_a?(Async::Queue)
346
+ loop do
347
+ chunk = mock_chunks.dequeue
348
+ break if chunk.nil?
349
+
350
+ chunks_read << chunk
351
+ yielder.call("#{chunk.to_json}\n")
352
+ end
353
+ else
354
+ mock_chunks.each do |chunk|
355
+ chunks_read << chunk
356
+ yielder.call("#{chunk.to_json}\n")
357
+ end
346
358
  end
347
359
  end
348
360
  end
@@ -429,18 +441,35 @@ describe ReactOnRailsProHelper do
429
441
 
430
442
  allow(mocked_stream).to receive(:write) do |chunk|
431
443
  written_chunks << chunk
432
- # Ensures that any chunk received is written immediately to the stream
433
- expect(written_chunks.count).to eq(chunks_read.count) # rubocop:disable RSpec/ExpectInHook
434
444
  end
435
445
  allow(mocked_stream).to receive(:close)
436
446
  mocked_response = instance_double(ActionDispatch::Response)
437
447
  allow(mocked_response).to receive(:stream).and_return(mocked_stream)
438
448
  allow(self).to receive(:response).and_return(mocked_response)
439
- mock_request_and_response
449
+ end
450
+
451
+ def execute_stream_view_containing_react_components
452
+ queue = Async::Queue.new
453
+ mock_request_and_response(queue)
454
+
455
+ Sync do |parent|
456
+ parent.async { stream_view_containing_react_components(template: template_path) }
457
+
458
+ chunks_to_write = chunks.dup
459
+ while (chunk = chunks_to_write.shift)
460
+ queue.enqueue(chunk)
461
+ sleep 0.05
462
+
463
+ # Ensures that any chunk received is written immediately to the stream
464
+ expect(written_chunks.count).to eq(chunks_read.count)
465
+ end
466
+ queue.close
467
+ sleep 0.05
468
+ end
440
469
  end
441
470
 
442
471
  it "writes the chunk to stream as soon as it is received" do
443
- stream_view_containing_react_components(template: template_path)
472
+ execute_stream_view_containing_react_components
444
473
  expect(self).to have_received(:render_to_string).once.with(template: template_path)
445
474
  expect(chunks_read.count).to eq(chunks.count)
446
475
  expect(written_chunks.count).to eq(chunks.count)
@@ -449,7 +478,7 @@ describe ReactOnRailsProHelper do
449
478
  end
450
479
 
451
480
  it "prepends the rails context to the first chunk only" do
452
- stream_view_containing_react_components(template: template_path)
481
+ execute_stream_view_containing_react_components
453
482
  initial_result = written_chunks.first
454
483
  expect(initial_result).to script_tag_be_included(rails_context_tag)
455
484
 
@@ -465,7 +494,7 @@ describe ReactOnRailsProHelper do
465
494
  end
466
495
 
467
496
  it "prepends the component specification tag to the first chunk only" do
468
- stream_view_containing_react_components(template: template_path)
497
+ execute_stream_view_containing_react_components
469
498
  initial_result = written_chunks.first
470
499
  expect(initial_result).to script_tag_be_included(react_component_specification_tag)
471
500
 
@@ -476,7 +505,7 @@ describe ReactOnRailsProHelper do
476
505
  end
477
506
 
478
507
  it "renders the rails view content in the first chunk" do
479
- stream_view_containing_react_components(template: template_path)
508
+ execute_stream_view_containing_react_components
480
509
  initial_result = written_chunks.first
481
510
  expect(initial_result).to include("<h1>Header Rendered In View</h1>")
482
511
  written_chunks[1..].each do |chunk|
@@ -53,7 +53,7 @@ RSpec.configure do |config|
53
53
 
54
54
  # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
55
55
  # For React on Rails Pro, using loadable-stats.json
56
- config.fixture_paths = ["#{::Rails.root}/spec/fixtures"]
56
+ config.fixture_paths = ["#{Rails.root}/spec/fixtures"]
57
57
 
58
58
  # If you're not using ActiveRecord, or you'd prefer not to run each of your
59
59
  # examples within a transaction, remove the following line or assign false
@@ -15,11 +15,11 @@ describe "Console logging from server" do
15
15
  html_nodes = Nokogiri::HTML(response.body)
16
16
  expected = <<~JS
17
17
  console.log.apply(console, ["[SERVER] RENDERED ReduxSharedStoreApp to dom node with id: ReduxSharedStoreApp-react-component-0"]);
18
- console.log.apply(console, ["[SERVER] This is a script:\\\"</div>\\\"(/script> <script>alert('WTF1')(/script>"]);
19
- console.log.apply(console, ["[SERVER] Script2:\\\"</div>\\\"(/script xx> <script>alert('WTF2')(/script xx>"]);
20
- console.log.apply(console, ["[SERVER] Script3:\\\"</div>\\\"(/script xx> <script>alert('WTF3')(/script xx>"]);
21
- console.log.apply(console, ["[SERVER] Script4\\\"</div>\\\"(/script <script>alert('WTF4')(/script>"]);
22
- console.log.apply(console, ["[SERVER] Script5:\\\"</div>\\\"(/script> <script>alert('WTF5')(/script>"]);
18
+ console.log.apply(console, ["[SERVER] This is a script:\\"</div>\\"(/script> <script>alert('WTF1')(/script>"]);
19
+ console.log.apply(console, ["[SERVER] Script2:\\"</div>\\"(/script xx> <script>alert('WTF2')(/script xx>"]);
20
+ console.log.apply(console, ["[SERVER] Script3:\\"</div>\\"(/script xx> <script>alert('WTF3')(/script xx>"]);
21
+ console.log.apply(console, ["[SERVER] Script4\\"</div>\\"(/script <script>alert('WTF4')(/script>"]);
22
+ console.log.apply(console, ["[SERVER] Script5:\\"</div>\\"(/script> <script>alert('WTF5')(/script>"]);
23
23
  console.log.apply(console, ["[SERVER] railsContext.serverSide is ","true"]);
24
24
  JS
25
25
 
@@ -110,7 +110,7 @@ describe "Turbolinks across pages", :js do
110
110
  it "changes name in message according to input" do
111
111
  visit "/client_side_hello_world"
112
112
  change_text_expect_dom_selector("#HelloWorld-react-component-0")
113
- click_link "Hello World Component Server Rendered, with extra options" # rubocop:disable Capybara/ClickLinkOrButtonStyle
113
+ click_on "Hello World Component Server Rendered, with extra options"
114
114
  change_text_expect_dom_selector("#my-hello-world-id")
115
115
  end
116
116
  end
@@ -174,19 +174,19 @@ describe "React Router", :js do
174
174
 
175
175
  before do
176
176
  visit "/"
177
- click_link "React Router" # rubocop:disable Capybara/ClickLinkOrButtonStyle
177
+ click_on "React Router"
178
178
  end
179
179
 
180
180
  context "when rendering /react_router" do
181
181
  it { is_expected.to have_text("Woohoo, we can use react-router here!") }
182
182
 
183
183
  it "clicking links correctly renders other pages" do
184
- click_link "Router First Page" # rubocop:disable Capybara/ClickLinkOrButtonStyle
184
+ click_on "Router First Page"
185
185
  expect(page).to have_current_path("/react_router/first_page")
186
186
  first_page_header_text = page.find(:css, "h2#first-page").text
187
187
  expect(first_page_header_text).to eq("React Router First Page")
188
188
 
189
- click_link "Router Second Page" # rubocop:disable Capybara/ClickLinkOrButtonStyle
189
+ click_on "Router Second Page"
190
190
  expect(page).to have_current_path("/react_router/second_page")
191
191
  second_page_header_text = page.find(:css, "h2#second-page").text
192
192
  expect(second_page_header_text).to eq("React Router Second Page")
@@ -244,7 +244,7 @@ describe "Manual client hydration", :js do
244
244
 
245
245
  it "HelloWorldRehydratable onChange should trigger" do
246
246
  within("form") do
247
- click_button "refresh" # rubocop:disable Capybara/ClickLinkOrButtonStyle
247
+ click_on "refresh"
248
248
  end
249
249
  within("#HelloWorldRehydratable-react-component-1") do
250
250
  find("input").set "Should update"
@@ -396,14 +396,14 @@ shared_examples "streamed component tests" do |path, selector|
396
396
 
397
397
  it "hydrates the component" do
398
398
  visit path
399
- expect(page.html).to include("client-bundle.js")
399
+ expect(page.html).to match(/client-bundle[^"]*.js/)
400
400
  change_text_expect_dom_selector(selector)
401
401
  end
402
402
 
403
403
  it "renders the page completely on server and displays content on client even without JavaScript" do
404
404
  # Don't add client-bundle.js to the page to ensure that the app is not hydrated
405
405
  visit "#{path}?skip_js_packs=true"
406
- expect(page.html).not_to include("client-bundle.js")
406
+ expect(page.html).not_to match(/client-bundle[^"]*.js/)
407
407
  # Ensure that the component state is not updated
408
408
  change_text_expect_dom_selector(selector, expect_no_change: true)
409
409
 
@@ -422,13 +422,18 @@ shared_examples "streamed component tests" do |path, selector|
422
422
  end
423
423
  end
424
424
 
425
- describe "Pages/stream_async_components_for_testing", :js,
426
- skip: "Flaky test replaced by Playwright E2E tests in e2e-tests/streaming.spec.ts" do
425
+ describe "Pages/stream_async_components_for_testing", :js do
427
426
  it_behaves_like "streamed component tests", "/stream_async_components_for_testing",
428
427
  "#AsyncComponentsTreeForTesting-react-component-0"
429
428
  end
430
429
 
431
- describe "React Router Sixth Page", :js, skip: "Work in progress in another branch: justin808/fix-body-dup-retry" do
430
+ describe "React Router Sixth Page", :js do
432
431
  it_behaves_like "streamed component tests", "/server_router/streaming-server-component",
433
432
  "#ServerComponentRouter-react-component-0"
433
+
434
+ # Skip the test that fails without JavaScript - being addressed in another PR
435
+ it "renders the page completely on server and displays content on client even without JavaScript", # rubocop:disable RSpec/NoExpectationExample
436
+ skip: "Being addressed in another PR" do
437
+ # This test is overridden to skip it
438
+ end
434
439
  end
@@ -9,14 +9,14 @@ describe "Shared Redux store example", :server_rendering do
9
9
 
10
10
  context "with enabled JS", :js do
11
11
  it "Has correct heading and text inside the text input" do
12
- expect(page).to have_selector("h3", text: /\ARedux Hello, Mr. Server Side Rendering!\z/)
13
- expect(page).to have_selector("input[type='text'][value='Mr. Server Side Rendering']")
12
+ expect(page).to have_css("h3", text: /\ARedux Hello, Mr. Server Side Rendering!\z/)
13
+ expect(page).to have_css("input[type='text'][value='Mr. Server Side Rendering']")
14
14
  end
15
15
 
16
16
  it "updates header in reaction to text input changes" do
17
17
  new_value = "new value"
18
18
  all("input[type='text']")[0].set(new_value)
19
- expect(page).to have_selector("h3", text: /\ARedux Hello, #{new_value}!\z/)
19
+ expect(page).to have_css("h3", text: /\ARedux Hello, #{new_value}!\z/)
20
20
  end
21
21
  end
22
22
  end