react_on_rails 16.1.2 → 16.2.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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.rubocop.yml +85 -0
  4. data/Gemfile.development_dependencies +8 -7
  5. data/Gemfile.lock +158 -119
  6. data/Steepfile +56 -0
  7. data/lib/generators/react_on_rails/base_generator.rb +43 -120
  8. data/lib/generators/react_on_rails/dev_tests_generator.rb +2 -1
  9. data/lib/generators/react_on_rails/generator_helper.rb +102 -2
  10. data/lib/generators/react_on_rails/install_generator.rb +36 -156
  11. data/lib/generators/react_on_rails/js_dependency_manager.rb +383 -0
  12. data/lib/generators/react_on_rails/templates/base/base/.dev-services.yml.example +76 -0
  13. data/lib/generators/react_on_rails/templates/base/base/bin/shakapacker-precompile-hook +30 -0
  14. data/lib/generators/react_on_rails/templates/base/base/bin/switch-bundler +141 -0
  15. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +44 -45
  16. data/lib/generators/react_on_rails/templates/base/base/config/{shakapacker.yml → shakapacker.yml.tt} +28 -3
  17. data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +15 -9
  18. data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +42 -6
  19. data/lib/react_on_rails/configuration.rb +149 -32
  20. data/lib/react_on_rails/controller.rb +3 -3
  21. data/lib/react_on_rails/dev/pack_generator.rb +168 -2
  22. data/lib/react_on_rails/dev/process_manager.rb +136 -14
  23. data/lib/react_on_rails/dev/server_manager.rb +194 -26
  24. data/lib/react_on_rails/dev/service_checker.rb +200 -0
  25. data/lib/react_on_rails/doctor.rb +341 -12
  26. data/lib/react_on_rails/engine.rb +75 -1
  27. data/lib/react_on_rails/git_utils.rb +3 -1
  28. data/lib/react_on_rails/helper.rb +70 -192
  29. data/lib/react_on_rails/locales/base.rb +17 -5
  30. data/lib/react_on_rails/packer_utils.rb +79 -2
  31. data/lib/react_on_rails/packs_generator.rb +57 -39
  32. data/lib/react_on_rails/prerender_error.rb +74 -17
  33. data/lib/react_on_rails/pro_helper.rb +64 -0
  34. data/lib/react_on_rails/react_component/render_options.rb +7 -7
  35. data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +2 -5
  36. data/lib/react_on_rails/smart_error.rb +326 -0
  37. data/lib/react_on_rails/system_checker.rb +8 -9
  38. data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +16 -7
  39. data/lib/react_on_rails/utils.rb +241 -55
  40. data/lib/react_on_rails/version.rb +1 -1
  41. data/lib/react_on_rails/version_checker.rb +383 -35
  42. data/lib/tasks/generate_packs.rake +12 -6
  43. data/lib/tasks/locale.rake +6 -1
  44. data/rakelib/docker.rake +26 -0
  45. data/rakelib/dummy_apps.rake +30 -0
  46. data/rakelib/example_type.rb +121 -0
  47. data/rakelib/examples_config.yml +52 -0
  48. data/rakelib/lint.rake +52 -0
  49. data/rakelib/node_package.rake +15 -0
  50. data/rakelib/rbs.rake +70 -0
  51. data/rakelib/run_rspec.rake +223 -0
  52. data/rakelib/shakapacker_examples.rake +171 -0
  53. data/rakelib/task_helpers.rb +134 -0
  54. data/rakelib/update_changelog.rake +73 -0
  55. data/react_on_rails.gemspec +4 -3
  56. data/sig/README.md +52 -0
  57. data/sig/react_on_rails/configuration.rbs +96 -0
  58. data/sig/react_on_rails/controller.rbs +15 -0
  59. data/sig/react_on_rails/dev/file_manager.rbs +15 -0
  60. data/sig/react_on_rails/dev/pack_generator.rbs +19 -0
  61. data/sig/react_on_rails/dev/process_manager.rbs +22 -0
  62. data/sig/react_on_rails/dev/server_manager.rbs +39 -0
  63. data/sig/react_on_rails/dev/service_checker.rbs +22 -0
  64. data/sig/react_on_rails/error.rbs +4 -0
  65. data/sig/react_on_rails/generators/js_dependency_manager.rbs +123 -0
  66. data/sig/react_on_rails/git_utils.rbs +8 -0
  67. data/sig/react_on_rails/helper.rbs +65 -0
  68. data/sig/react_on_rails/json_parse_error.rbs +10 -0
  69. data/sig/react_on_rails/locales.rbs +46 -0
  70. data/sig/react_on_rails/packer_utils.rbs +15 -0
  71. data/sig/react_on_rails/prerender_error.rbs +21 -0
  72. data/sig/react_on_rails/server_rendering_pool.rbs +12 -0
  73. data/sig/react_on_rails/smart_error.rbs +28 -0
  74. data/sig/react_on_rails/test_helper.rbs +11 -0
  75. data/sig/react_on_rails/utils.rbs +34 -0
  76. data/sig/react_on_rails/version_checker.rbs +12 -0
  77. data/sig/react_on_rails.rbs +17 -0
  78. metadata +49 -32
  79. data/AI_AGENT_INSTRUCTIONS.md +0 -63
  80. data/CHANGELOG.md +0 -1836
  81. data/CLAUDE.md +0 -135
  82. data/CODING_AGENTS.md +0 -313
  83. data/CONTRIBUTING.md +0 -668
  84. data/Dockerfile_tests +0 -12
  85. data/KUDOS.md +0 -114
  86. data/LICENSE.md +0 -47
  87. data/LICENSES/README.md +0 -14
  88. data/NEWS.md +0 -62
  89. data/PROJECTS.md +0 -63
  90. data/REACT-ON-RAILS-PRO-LICENSE.md +0 -129
  91. data/README.md +0 -217
  92. data/SUMMARY.md +0 -88
  93. data/TODO.md +0 -135
  94. data/bin/lefthook/check-trailing-newlines +0 -38
  95. data/bin/lefthook/get-changed-files +0 -26
  96. data/bin/lefthook/prettier-format +0 -26
  97. data/bin/lefthook/ruby-autofix +0 -26
  98. data/bin/lefthook/ruby-lint +0 -27
  99. data/docker-compose.yml +0 -11
  100. data/eslint.config.ts +0 -232
  101. data/knip.ts +0 -114
  102. data/lib/react_on_rails/pro/NOTICE +0 -21
  103. data/lib/react_on_rails/pro/helper.rb +0 -122
  104. data/lib/react_on_rails/pro/utils.rb +0 -53
  105. data/tsconfig.eslint.json +0 -6
  106. data/tsconfig.json +0 -19
@@ -1,51 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # See https://github.com/shakacode/react_on_rails/blob/master/docs/guides/configuration.md
4
- # for many more options.
3
+ # React on Rails configuration
4
+ # See https://github.com/shakacode/react_on_rails/blob/master/docs/configuration/configuration.md
5
+ # for complete documentation of all configuration options.
5
6
 
6
7
  ReactOnRails.configure do |config|
7
- # This configures the script to run to build the production assets by webpack. Set this to nil
8
- # if you don't want react_on_rails building this file for you.
9
- # If nil, then the standard shakacode/shakapacker assets:precompile will run
10
- # config.build_production_command = nil
11
-
12
- ################################################################################
13
8
  ################################################################################
14
- # TEST CONFIGURATION OPTIONS
15
- # Below options are used with the use of this test helper:
16
- # ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
9
+ # Server Rendering (Recommended)
17
10
  ################################################################################
11
+ # Configure server bundle for server-side rendering with `prerender: true`
12
+ # Set to "" if you're not using server rendering
13
+ config.server_bundle_js_file = "server-bundle.js"
18
14
 
19
- # If you are using this in your spec_helper.rb (or rails_helper.rb):
15
+ # ⚠️ RECOMMENDED: Use Shakapacker 9.0+ private_output_path instead
20
16
  #
21
- # ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
17
+ # If using Shakapacker 9.0+, add to config/shakapacker.yml:
18
+ # private_output_path: ssr-generated
22
19
  #
23
- # with rspec then this controls what npm command is run
24
- # to automatically refresh your webpack assets on every test run.
20
+ # React on Rails will auto-detect this value, eliminating the need to set it here.
21
+ # This keeps your webpack and Rails configs in sync automatically.
25
22
  #
26
- # Alternately, you can remove the `ReactOnRails::TestHelper.configure_rspec_to_compile_assets`
27
- # and set the config/shakapacker.yml option for test to true.
28
- config.build_test_command = "RAILS_ENV=test bin/shakapacker"
29
-
30
- ################################################################################
31
- ################################################################################
32
- # SERVER RENDERING OPTIONS
33
- ################################################################################
34
- # This is the file used for server rendering of React when using `(prerender: true)`
35
- # If you are never using server rendering, you should set this to "".
36
- # Note, there is only one server bundle, unlike JavaScript where you want to minimize the size
37
- # of the JS sent to the client. For the server rendering, React on Rails creates a pool of
38
- # JavaScript execution instances which should handle any component requested.
39
- #
40
- # While you may configure this to be the same as your client bundle file, this file is typically
41
- # different. You should have ONE server bundle which can create all of your server rendered
42
- # React components.
23
+ # For older Shakapacker versions or custom setups, manually configure:
24
+ # config.server_bundle_output_path = "ssr-generated"
43
25
  #
44
- config.server_bundle_js_file = "server-bundle.js"
45
-
46
- # Configure where server bundles are output. Defaults to "ssr-generated".
47
- # This should match your webpack configuration for server bundles.
48
- config.server_bundle_output_path = "ssr-generated"
26
+ # The path is relative to Rails.root and should point to a private directory
27
+ # (outside of public/) for security. Run 'rails react_on_rails:doctor' to verify.
49
28
 
50
29
  # Enforce that server bundles are only loaded from private (non-public) directories.
51
30
  # When true, server bundles will only be loaded from the configured server_bundle_output_path.
@@ -53,15 +32,35 @@ ReactOnRails.configure do |config|
53
32
  config.enforce_private_server_bundles = true
54
33
 
55
34
  ################################################################################
35
+ # Test Configuration (Optional)
56
36
  ################################################################################
57
- # FILE SYSTEM BASED COMPONENT REGISTRY
58
- ################################################################################
59
- # `components_subdirectory` is the name of the matching directories that contain automatically registered components
60
- # for use in the Rails views. The default is nil, you can enable the feature by updating it in the next line.
61
- config.components_subdirectory = "ror_components"
37
+ # ⚠️ IMPORTANT: Two mutually exclusive approaches - use ONLY ONE:
38
+ #
39
+ # RECOMMENDED APPROACH: Set `compile: true` in config/shakapacker.yml test section
40
+ # - Simpler configuration (no additional setup needed)
41
+ # - Handled automatically by Shakapacker
62
42
  #
63
- # For automated component registry, `render_component` view helper method tries to load bundle for component from
64
- # generated directory. default is false, you can pass option at the time of individual usage or update the default
65
- # in the following line
43
+ # ALTERNATIVE APPROACH: Uncomment below AND configure ReactOnRails::TestHelper
44
+ # - Provides explicit control over test asset compilation timing
45
+ # - Requires adding ReactOnRails::TestHelper to spec/rails_helper.rb
46
+ # - See: https://github.com/shakacode/react_on_rails/blob/master/docs/guides/testing-configuration.md
47
+ #
48
+ config.build_test_command = "RAILS_ENV=test bin/shakapacker"
49
+
66
50
  config.auto_load_bundle = true
51
+ config.components_subdirectory = "ror_components"
52
+ ################################################################################
53
+ # Advanced Configuration
54
+ ################################################################################
55
+ # Most configuration options have sensible defaults and don't need to be set.
56
+ # For advanced options including:
57
+ # - File-based component registry (components_subdirectory, auto_load_bundle)
58
+ # - Component loading strategies (async/defer/sync)
59
+ # - Server bundle security and organization
60
+ # - I18n configuration
61
+ # - Server rendering pool configuration
62
+ # - Custom rendering extensions
63
+ # - And more...
64
+ #
65
+ # See: https://github.com/shakacode/react_on_rails/blob/master/docs/configuration/configuration.md
67
66
  end
@@ -29,6 +29,15 @@ default: &default
29
29
  # Location for manifest.json, defaults to {public_output_path}/manifest.json if unset
30
30
  # manifest_path: public/packs/manifest.json
31
31
 
32
+ # Location for private server-side bundles (e.g., for SSR)
33
+ # These bundles are not served publicly, unlike public_output_path
34
+ # Shakapacker 9.0+ feature - automatically detected by React on Rails
35
+ <% if shakapacker_version_9_or_higher? -%>
36
+ private_output_path: ssr-generated
37
+ <% else -%>
38
+ # private_output_path: ssr-generated # Uncomment to enable (requires Shakapacker 9.0+)
39
+ <% end -%>
40
+
32
41
  # Additional paths webpack should look up modules
33
42
  # ['app/assets', 'engine/foo/app/assets']
34
43
  additional_paths: []
@@ -36,12 +45,24 @@ default: &default
36
45
  # Reload manifest.json on all requests so we reload latest compiled packs
37
46
  cache_manifest: false
38
47
 
39
- # Select loader to use, available options are 'babel' (default), 'swc' or 'esbuild'
40
- webpack_loader: 'babel'
48
+ # Select JavaScript transpiler to use
49
+ # Available options: 'swc' (default, 20x faster), 'babel', 'esbuild', or 'none'
50
+ # Use 'none' when providing a completely custom webpack configuration
51
+ # Note: When using rspack, swc is used automatically regardless of this setting
52
+ javascript_transpiler: "swc"
53
+
54
+ # Select assets bundler to use
55
+ # Available options: 'webpack' (default) or 'rspack'
56
+ assets_bundler: "webpack"
41
57
 
42
58
  # Raises an error if there is a mismatch in the shakapacker gem and npm package being used
43
59
  ensure_consistent_versioning: true
44
60
 
61
+ # Hook to run before webpack compilation (e.g., for generating dynamic entry points)
62
+ # SECURITY: Only reference trusted scripts within your project. Ensure the hook path
63
+ # points to a file within the project root that you control.
64
+ precompile_hook: 'bin/shakapacker-precompile-hook'
65
+
45
66
  # Select whether the compiler will use SHA digest ('digest' option) or most recent modified timestamp ('mtime') to determine freshness
46
67
  compiler_strategy: digest
47
68
 
@@ -50,6 +71,11 @@ default: &default
50
71
  # https://webpack.js.org/guides/build-performance/#avoid-production-specific-tooling
51
72
  useContentHash: false
52
73
 
74
+ # On-demand compilation of packs when modified. Defaults to false.
75
+ # Set to false if using bin/shakapacker-dev-server or bin/shakapacker --watch via Procfiles.
76
+ # Set to true only in test environment for on-demand compilation.
77
+ compile: false
78
+
53
79
  # Setting the asset host here will override Rails.application.config.asset_host.
54
80
  # Here, you can set different asset_host per environment. Note that
55
81
  # SHAKAPACKER_ASSET_HOST will override both configurations.
@@ -67,7 +93,6 @@ default: &default
67
93
 
68
94
  development:
69
95
  <<: *default
70
- compile: true
71
96
  compiler_strategy: mtime
72
97
 
73
98
  # Reference: https://webpack.js.org/configuration/dev-server/
@@ -1,20 +1,26 @@
1
1
  <%= add_documentation_reference(config[:message], "// https://github.com/shakacode/react_on_rails_demo_ssr_hmr/blob/master/config/webpack/development.js") %>
2
2
 
3
- const { devServer, inliningCss } = require('shakapacker');
3
+ const { devServer, inliningCss, config } = require('shakapacker');
4
4
 
5
5
  const generateWebpackConfigs = require('./generateWebpackConfigs');
6
6
 
7
7
  const developmentEnvOnly = (clientWebpackConfig, _serverWebpackConfig) => {
8
- // React Refresh (Fast Refresh) setup - only when webpack-dev-server is running (HMR mode)
9
- // This matches the condition in generateWebpackConfigs.js and babel.config.js
8
+ // React Refresh (Fast Refresh) setup - only when dev server is running (HMR mode)
10
9
  if (process.env.WEBPACK_SERVE) {
11
10
  // eslint-disable-next-line global-require
12
- const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
13
- clientWebpackConfig.plugins.push(
14
- new ReactRefreshWebpackPlugin({
15
- // Use default overlay configuration for better compatibility
16
- }),
17
- );
11
+ if (config.assets_bundler === 'rspack') {
12
+ // Rspack uses @rspack/plugin-react-refresh for React Fast Refresh
13
+ const ReactRefreshPlugin = require('@rspack/plugin-react-refresh');
14
+ clientWebpackConfig.plugins.push(new ReactRefreshPlugin());
15
+ } else {
16
+ // Webpack uses @pmmmwh/react-refresh-webpack-plugin
17
+ const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
18
+ clientWebpackConfig.plugins.push(
19
+ new ReactRefreshWebpackPlugin({
20
+ // Use default overlay configuration for better compatibility
21
+ }),
22
+ );
23
+ }
18
24
  }
19
25
  };
20
26
 
@@ -3,7 +3,9 @@
3
3
  const { merge, config } = require('shakapacker');
4
4
  const commonWebpackConfig = require('./commonWebpackConfig');
5
5
 
6
- const webpack = require('webpack');
6
+ const bundler = config.assets_bundler === 'rspack'
7
+ ? require('@rspack/core')
8
+ : require('webpack');
7
9
 
8
10
  const configureServer = () => {
9
11
  // We need to use "merge" because the clientConfigObject, EVEN after running
@@ -40,21 +42,55 @@ const configureServer = () => {
40
42
  serverWebpackConfig.optimization = {
41
43
  minimize: false,
42
44
  };
43
- serverWebpackConfig.plugins.unshift(new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }));
45
+ serverWebpackConfig.plugins.unshift(new bundler.optimize.LimitChunkCountPlugin({ maxChunks: 1 }));
46
+
47
+ // Custom output for the server-bundle
48
+ <% if shakapacker_version_9_or_higher? -%>
49
+ // Using Shakapacker 9.0+ privateOutputPath for automatic sync with shakapacker.yml
50
+ // This eliminates manual path configuration and keeps configs in sync.
51
+ // Falls back to hardcoded path if private_output_path is not configured.
52
+ const serverBundleOutputPath = config.privateOutputPath ||
53
+ require('path').resolve(__dirname, '../../ssr-generated');
54
+ <% else -%>
55
+ // Using hardcoded path (Shakapacker < 9.0)
56
+ // For Shakapacker 9.0+, consider using config.privateOutputPath instead
57
+ // to automatically sync with shakapacker.yml private_output_path.
58
+ const serverBundleOutputPath = require('path').resolve(__dirname, '../../ssr-generated');
59
+ <% end -%>
44
60
 
45
- // Custom output for the server-bundle that matches the config in
46
- // config/initializers/react_on_rails.rb
47
- // Server bundles are output to a private directory (not public) for security
48
61
  serverWebpackConfig.output = {
49
62
  filename: 'server-bundle.js',
50
63
  globalObject: 'this',
51
64
  // If using the React on Rails Pro node server renderer, uncomment the next line
52
65
  // libraryTarget: 'commonjs2',
53
- path: require('path').resolve(__dirname, '../../ssr-generated'),
66
+ path: serverBundleOutputPath,
54
67
  // No publicPath needed since server bundles are not served via web
55
68
  // https://webpack.js.org/configuration/output/#outputglobalobject
56
69
  };
57
70
 
71
+ // Validate server bundle output path configuration
72
+ <% if shakapacker_version_9_or_higher? -%>
73
+ // For Shakapacker 9.0+, verify privateOutputPath is configured in shakapacker.yml
74
+ if (!config.privateOutputPath) {
75
+ console.warn('⚠️ Shakapacker 9.0+ detected but private_output_path not configured in shakapacker.yml');
76
+ console.warn(' Add to config/shakapacker.yml:');
77
+ console.warn(' private_output_path: ssr-generated');
78
+ console.warn(' Run: rails react_on_rails:doctor to validate your configuration');
79
+ }
80
+ <% else -%>
81
+ // For Shakapacker < 9.0, verify hardcoded path syncs with Rails config
82
+ // 1. Ensure config/initializers/react_on_rails.rb has: config.server_bundle_output_path = "ssr-generated"
83
+ // 2. Run: rails react_on_rails:doctor to verify configuration
84
+ const fs = require('fs');
85
+ if (!fs.existsSync(serverBundleOutputPath)) {
86
+ console.warn(`⚠️ Server bundle output directory does not exist: ${serverBundleOutputPath}`);
87
+ console.warn(' It will be created during build, but ensure React on Rails is configured:');
88
+ console.warn(' config.server_bundle_output_path = "ssr-generated" in config/initializers/react_on_rails.rb');
89
+ console.warn(' Run: rails react_on_rails:doctor to validate your configuration');
90
+ }
91
+ <% end -%>
92
+
93
+
58
94
  // Don't hash the server bundle b/c would conflict with the client manifest
59
95
  // And no need for the MiniCssExtractPlugin
60
96
  serverWebpackConfig.plugins = serverWebpackConfig.plugins.filter(
@@ -1,5 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/enumerable"
4
+ require "active_support/core_ext/object/blank"
5
+
6
+ # Polyfill for compact_blank (added in Rails 6.1) to support Rails 5.2-6.0
7
+ unless [].respond_to?(:compact_blank)
8
+ module Enumerable
9
+ def compact_blank
10
+ reject(&:blank?)
11
+ end
12
+ end
13
+
14
+ class Array
15
+ def compact_blank
16
+ reject(&:blank?)
17
+ end
18
+ end
19
+ end
20
+
3
21
  # rubocop:disable Metrics/ClassLength
4
22
 
5
23
  module ReactOnRails
@@ -9,9 +27,8 @@ module ReactOnRails
9
27
  end
10
28
 
11
29
  DEFAULT_GENERATED_ASSETS_DIR = File.join(%w[public webpack], Rails.env).freeze
12
- DEFAULT_REACT_CLIENT_MANIFEST_FILE = "react-client-manifest.json"
13
- DEFAULT_REACT_SERVER_CLIENT_MANIFEST_FILE = "react-server-client-manifest.json"
14
30
  DEFAULT_COMPONENT_REGISTRY_TIMEOUT = 5000
31
+ DEFAULT_SERVER_BUNDLE_OUTPUT_PATH = "ssr-generated"
15
32
 
16
33
  def self.configuration
17
34
  @configuration ||= Configuration.new(
@@ -20,9 +37,6 @@ module ReactOnRails
20
37
  # generated_assets_dirs is deprecated
21
38
  generated_assets_dir: "",
22
39
  server_bundle_js_file: "",
23
- rsc_bundle_js_file: "",
24
- react_client_manifest_file: DEFAULT_REACT_CLIENT_MANIFEST_FILE,
25
- react_server_client_manifest_file: DEFAULT_REACT_SERVER_CLIENT_MANIFEST_FILE,
26
40
  prerender: false,
27
41
  auto_load_bundle: false,
28
42
  replay_console: true,
@@ -46,19 +60,14 @@ module ReactOnRails
46
60
  components_subdirectory: nil,
47
61
  make_generated_server_bundle_the_entrypoint: false,
48
62
  defer_generated_component_packs: false,
49
- # React on Rails Pro (licensed) feature - enables immediate hydration of React components
50
- immediate_hydration: false,
51
63
  # Maximum time in milliseconds to wait for client-side component registration after page load.
52
64
  # If exceeded, an error will be thrown for server-side rendered components not registered on the client.
53
65
  # Set to 0 to disable the timeout and wait indefinitely for component registration.
54
66
  component_registry_timeout: DEFAULT_COMPONENT_REGISTRY_TIMEOUT,
55
67
  generated_component_packs_loading_strategy: nil,
56
- server_bundle_output_path: "ssr-generated",
68
+ server_bundle_output_path: DEFAULT_SERVER_BUNDLE_OUTPUT_PATH,
57
69
  enforce_private_server_bundles: false
58
70
  )
59
- # TODO: Add automatic detection of server_bundle_output_path from shakapacker.yml
60
- # See feature/shakapacker-yml-integration branch for implementation
61
- # Requires Shakapacker v8.5.0+ and semantic version checking
62
71
  end
63
72
 
64
73
  class Configuration
@@ -72,10 +81,55 @@ module ReactOnRails
72
81
  :server_render_method, :random_dom_id, :auto_load_bundle,
73
82
  :same_bundle_for_client_and_server, :rendering_props_extension,
74
83
  :make_generated_server_bundle_the_entrypoint,
75
- :generated_component_packs_loading_strategy, :immediate_hydration, :rsc_bundle_js_file,
76
- :react_client_manifest_file, :react_server_client_manifest_file, :component_registry_timeout,
84
+ :generated_component_packs_loading_strategy,
85
+ :component_registry_timeout,
77
86
  :server_bundle_output_path, :enforce_private_server_bundles
78
87
 
88
+ # Class instance variable and mutex to track if deprecation warning has been shown
89
+ # Using mutex to ensure thread-safety in multi-threaded environments
90
+ @immediate_hydration_warned = false
91
+ @immediate_hydration_mutex = Mutex.new
92
+
93
+ class << self
94
+ attr_accessor :immediate_hydration_warned, :immediate_hydration_mutex
95
+ end
96
+
97
+ # Deprecated: immediate_hydration configuration has been removed
98
+ def immediate_hydration=(value)
99
+ warned = false
100
+ self.class.immediate_hydration_mutex.synchronize do
101
+ warned = self.class.immediate_hydration_warned
102
+ self.class.immediate_hydration_warned = true unless warned
103
+ end
104
+
105
+ return if warned
106
+
107
+ Rails.logger.warn <<~WARNING
108
+ [REACT ON RAILS] The 'config.immediate_hydration' configuration option is deprecated and no longer used.
109
+ Immediate hydration is now automatically enabled for React on Rails Pro users.
110
+ Please remove 'config.immediate_hydration = #{value}' from your config/initializers/react_on_rails.rb file.
111
+ See CHANGELOG.md for migration instructions.
112
+ WARNING
113
+ end
114
+
115
+ def immediate_hydration
116
+ warned = false
117
+ self.class.immediate_hydration_mutex.synchronize do
118
+ warned = self.class.immediate_hydration_warned
119
+ self.class.immediate_hydration_warned = true unless warned
120
+ end
121
+
122
+ return nil if warned
123
+
124
+ Rails.logger.warn <<~WARNING
125
+ [REACT ON RAILS] The 'config.immediate_hydration' configuration option is deprecated and no longer used.
126
+ Immediate hydration is now automatically enabled for React on Rails Pro users.
127
+ Please remove any references to 'config.immediate_hydration' from your config/initializers/react_on_rails.rb file.
128
+ See CHANGELOG.md for migration instructions.
129
+ WARNING
130
+ nil
131
+ end
132
+
79
133
  # rubocop:disable Metrics/AbcSize
80
134
  def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender: nil,
81
135
  replay_console: nil, make_generated_server_bundle_the_entrypoint: nil,
@@ -89,8 +143,7 @@ module ReactOnRails
89
143
  same_bundle_for_client_and_server: nil,
90
144
  i18n_dir: nil, i18n_yml_dir: nil, i18n_output_format: nil, i18n_yml_safe_load_options: nil,
91
145
  random_dom_id: nil, server_render_method: nil, rendering_props_extension: nil,
92
- components_subdirectory: nil, auto_load_bundle: nil, immediate_hydration: nil,
93
- rsc_bundle_js_file: nil, react_client_manifest_file: nil, react_server_client_manifest_file: nil,
146
+ components_subdirectory: nil, auto_load_bundle: nil,
94
147
  component_registry_timeout: nil, server_bundle_output_path: nil, enforce_private_server_bundles: nil)
95
148
  self.node_modules_location = node_modules_location.present? ? node_modules_location : Rails.root
96
149
  self.generated_assets_dirs = generated_assets_dirs
@@ -119,9 +172,6 @@ module ReactOnRails
119
172
 
120
173
  # Server rendering:
121
174
  self.server_bundle_js_file = server_bundle_js_file
122
- self.rsc_bundle_js_file = rsc_bundle_js_file
123
- self.react_client_manifest_file = react_client_manifest_file
124
- self.react_server_client_manifest_file = react_server_client_manifest_file
125
175
  self.same_bundle_for_client_and_server = same_bundle_for_client_and_server
126
176
  self.server_renderer_pool_size = self.development_mode ? 1 : server_renderer_pool_size
127
177
  self.server_renderer_timeout = server_renderer_timeout # seconds
@@ -134,7 +184,6 @@ module ReactOnRails
134
184
  self.auto_load_bundle = auto_load_bundle
135
185
  self.make_generated_server_bundle_the_entrypoint = make_generated_server_bundle_the_entrypoint
136
186
  self.defer_generated_component_packs = defer_generated_component_packs
137
- self.immediate_hydration = immediate_hydration
138
187
  self.generated_component_packs_loading_strategy = generated_component_packs_loading_strategy
139
188
  self.server_bundle_output_path = server_bundle_output_path
140
189
  self.enforce_private_server_bundles = enforce_private_server_bundles
@@ -154,6 +203,7 @@ module ReactOnRails
154
203
  check_component_registry_timeout
155
204
  validate_generated_component_packs_loading_strategy
156
205
  validate_enforce_private_server_bundles
206
+ auto_detect_server_bundle_path_from_shakapacker
157
207
  end
158
208
 
159
209
  private
@@ -184,12 +234,13 @@ module ReactOnRails
184
234
 
185
235
  msg = <<~MSG
186
236
  ReactOnRails: Your current version of shakapacker \
187
- does not support async script loading, which may cause performance issues. Please either:
188
- 1. Use :sync or :defer loading strategy instead of :async
237
+ does not support async script loading. Please either:
238
+ 1. Use :defer or :sync loading strategy instead of :async
189
239
  2. Upgrade to Shakapacker v8.2.0 or above to enable async script loading
190
240
  MSG
191
241
  if PackerUtils.supports_async_loading?
192
- self.generated_component_packs_loading_strategy ||= :async
242
+ # Default based on Pro license: Pro users get :async, non-Pro users get :defer
243
+ self.generated_component_packs_loading_strategy ||= (Utils.react_on_rails_pro? ? :async : :defer)
193
244
  elsif generated_component_packs_loading_strategy.nil?
194
245
  Rails.logger.warn("**WARNING** #{msg}")
195
246
  self.generated_component_packs_loading_strategy = :sync
@@ -226,6 +277,57 @@ module ReactOnRails
226
277
  "the public directory. Please set it to a directory outside of public."
227
278
  end
228
279
 
280
+ # Auto-detect server_bundle_output_path from Shakapacker 9.0+ private_output_path
281
+ # Checks if user explicitly set a value and warns them to use auto-detection instead
282
+ def auto_detect_server_bundle_path_from_shakapacker
283
+ # Skip if Shakapacker is not available
284
+ return unless defined?(::Shakapacker)
285
+
286
+ # Check if Shakapacker config has private_output_path method (9.0+)
287
+ return unless ::Shakapacker.config.respond_to?(:private_output_path)
288
+
289
+ # Get the private_output_path from Shakapacker
290
+ private_path = ::Shakapacker.config.private_output_path
291
+ return unless private_path
292
+
293
+ relative_path = ReactOnRails::Utils.normalize_to_relative_path(private_path)
294
+
295
+ # Check if user explicitly configured server_bundle_output_path
296
+ if server_bundle_output_path != ReactOnRails::DEFAULT_SERVER_BUNDLE_OUTPUT_PATH
297
+ warn_about_explicit_configuration(relative_path)
298
+ return
299
+ end
300
+
301
+ apply_shakapacker_private_output_path(relative_path)
302
+ rescue StandardError => e
303
+ # Fail gracefully - if auto-detection fails, keep the default
304
+ Rails.logger&.debug("ReactOnRails: Could not auto-detect server bundle path from " \
305
+ "Shakapacker: #{e.message}")
306
+ end
307
+
308
+ def warn_about_explicit_configuration(shakapacker_path)
309
+ # Normalize both paths for comparison
310
+ normalized_config = server_bundle_output_path.to_s.chomp("/")
311
+ normalized_shakapacker = shakapacker_path.to_s.chomp("/")
312
+
313
+ # Only warn if there's a mismatch
314
+ return if normalized_config == normalized_shakapacker
315
+
316
+ Rails.logger&.warn(
317
+ "ReactOnRails: server_bundle_output_path is explicitly set to '#{server_bundle_output_path}' " \
318
+ "but shakapacker.yml private_output_path is '#{shakapacker_path}'. " \
319
+ "Consider removing server_bundle_output_path from your React on Rails initializer " \
320
+ "to use the auto-detected value from shakapacker.yml."
321
+ )
322
+ end
323
+
324
+ def apply_shakapacker_private_output_path(relative_path)
325
+ self.server_bundle_output_path = relative_path
326
+
327
+ Rails.logger&.debug("ReactOnRails: Auto-detected server_bundle_output_path from " \
328
+ "shakapacker.yml private_output_path: '#{relative_path}'")
329
+ end
330
+
229
331
  def check_minimum_shakapacker_version
230
332
  ReactOnRails::PackerUtils.raise_shakapacker_version_incompatible_for_basic_pack_generation unless
231
333
  ReactOnRails::PackerUtils.supports_basic_pack_generation?
@@ -247,7 +349,14 @@ module ReactOnRails
247
349
  raise(ReactOnRails::Error, compile_command_conflict_message) if ReactOnRails::PackerUtils.precompile?
248
350
 
249
351
  precompile_tasks = lambda {
250
- Rake::Task["react_on_rails:generate_packs"].invoke
352
+ # Skip generate_packs if shakapacker has a precompile hook configured
353
+ if ReactOnRails::PackerUtils.shakapacker_precompile_hook_configured?
354
+ hook_value = ReactOnRails::PackerUtils.shakapacker_precompile_hook_value
355
+ puts "Skipping react_on_rails:generate_packs (configured in shakapacker precompile hook: #{hook_value})"
356
+ else
357
+ Rake::Task["react_on_rails:generate_packs"].invoke
358
+ end
359
+
251
360
  Rake::Task["react_on_rails:assets:webpack"].invoke
252
361
 
253
362
  # VERSIONS is per the shakacode/shakapacker clean method definition.
@@ -320,15 +429,23 @@ module ReactOnRails
320
429
  end
321
430
 
322
431
  def ensure_webpack_generated_files_exists
323
- return unless webpack_generated_files.empty?
324
-
325
- self.webpack_generated_files = [
326
- "manifest.json",
327
- server_bundle_js_file,
328
- rsc_bundle_js_file,
329
- react_client_manifest_file,
330
- react_server_client_manifest_file
331
- ].compact_blank
432
+ all_required_files = ["manifest.json", server_bundle_js_file]
433
+
434
+ if ReactOnRails::Utils.react_on_rails_pro?
435
+ pro_config = ReactOnRailsPro.configuration
436
+ all_required_files << pro_config.rsc_bundle_js_file
437
+ all_required_files << pro_config.react_client_manifest_file
438
+ all_required_files << pro_config.react_server_client_manifest_file
439
+ end
440
+
441
+ all_required_files = all_required_files.compact_blank
442
+
443
+ if webpack_generated_files.empty?
444
+ self.webpack_generated_files = all_required_files
445
+ else
446
+ missing_files = all_required_files.reject { |file| webpack_generated_files.include?(file) }
447
+ self.webpack_generated_files += missing_files if missing_files.any?
448
+ end
332
449
  end
333
450
 
334
451
  def configure_skip_display_none_deprecation
@@ -9,13 +9,13 @@ module ReactOnRails
9
9
  # JavaScript code.
10
10
  # props: Named parameter props which is a Ruby Hash or JSON string which contains the properties
11
11
  # to pass to the redux store.
12
- # immediate_hydration: React on Rails Pro (licensed) feature. Pass as true if you wish to hydrate this
13
- # store immediately instead of waiting for the page to load.
12
+ # immediate_hydration: React on Rails Pro (licensed) feature. When nil (default), Pro users get
13
+ # immediate hydration, non-Pro users don't. Can be explicitly overridden.
14
14
  #
15
15
  # Be sure to include view helper `redux_store_hydration_data` at the end of your layout or view
16
16
  # or else there will be no client side hydration of your stores.
17
17
  def redux_store(store_name, props: {}, immediate_hydration: nil)
18
- immediate_hydration = ReactOnRails.configuration.immediate_hydration if immediate_hydration.nil?
18
+ immediate_hydration = ReactOnRails::Utils.normalize_immediate_hydration(immediate_hydration, store_name, "Store")
19
19
  redux_store_data = { store_name: store_name,
20
20
  props: props,
21
21
  immediate_hydration: immediate_hydration }