react_on_rails 14.2.1 โ†’ 16.1.1

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 (116) hide show
  1. checksums.yaml +4 -4
  2. data/AI_AGENT_INSTRUCTIONS.md +63 -0
  3. data/CHANGELOG.md +564 -85
  4. data/CLAUDE.md +135 -0
  5. data/CODING_AGENTS.md +313 -0
  6. data/CONTRIBUTING.md +448 -37
  7. data/Gemfile.development_dependencies +6 -1
  8. data/Gemfile.lock +13 -4
  9. data/KUDOS.md +22 -1
  10. data/LICENSE.md +30 -4
  11. data/LICENSES/README.md +14 -0
  12. data/NEWS.md +48 -48
  13. data/PROJECTS.md +45 -40
  14. data/REACT-ON-RAILS-PRO-LICENSE.md +129 -0
  15. data/README.md +113 -42
  16. data/SUMMARY.md +62 -52
  17. data/TODO.md +135 -0
  18. data/bin/lefthook/check-trailing-newlines +38 -0
  19. data/bin/lefthook/get-changed-files +26 -0
  20. data/bin/lefthook/prettier-format +26 -0
  21. data/bin/lefthook/ruby-autofix +26 -0
  22. data/bin/lefthook/ruby-lint +27 -0
  23. data/eslint.config.ts +232 -0
  24. data/knip.ts +40 -6
  25. data/lib/generators/USAGE +4 -5
  26. data/lib/generators/react_on_rails/USAGE +65 -0
  27. data/lib/generators/react_on_rails/base_generator.rb +276 -62
  28. data/lib/generators/react_on_rails/dev_tests_generator.rb +1 -0
  29. data/lib/generators/react_on_rails/generator_helper.rb +35 -1
  30. data/lib/generators/react_on_rails/generator_messages.rb +138 -17
  31. data/lib/generators/react_on_rails/install_generator.rb +474 -26
  32. data/lib/generators/react_on_rails/react_no_redux_generator.rb +19 -6
  33. data/lib/generators/react_on_rails/react_with_redux_generator.rb +110 -18
  34. data/lib/generators/react_on_rails/templates/.eslintrc +1 -1
  35. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev +5 -0
  36. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-prod-assets +8 -0
  37. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static-assets +2 -0
  38. data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -5
  39. data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +2 -2
  40. data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorldServer.js +1 -1
  41. data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/server-bundle.js +1 -8
  42. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx +21 -0
  43. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.tsx +25 -0
  44. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css +4 -0
  45. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.jsx +5 -0
  46. data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.tsx +5 -0
  47. data/lib/generators/react_on_rails/templates/base/base/app/views/hello_world/index.html.erb.tt +1 -1
  48. data/lib/generators/react_on_rails/templates/base/base/app/views/layouts/hello_world.html.erb +4 -2
  49. data/lib/generators/react_on_rails/templates/base/base/babel.config.js.tt +5 -2
  50. data/lib/generators/react_on_rails/templates/base/base/bin/dev +34 -0
  51. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +14 -5
  52. data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +76 -7
  53. data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +1 -1
  54. data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +6 -10
  55. data/lib/generators/react_on_rails/templates/base/base/config/webpack/production.js.tt +2 -2
  56. data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +3 -2
  57. data/lib/generators/react_on_rails/templates/base/base/config/webpack/test.js.tt +2 -2
  58. data/lib/generators/react_on_rails/templates/dev_tests/spec/system/hello_world_spec.rb +0 -2
  59. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.ts +18 -0
  60. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -6
  61. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +4 -0
  62. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx +24 -0
  63. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/constants/helloWorldConstants.ts +6 -0
  64. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/containers/HelloWorldContainer.ts +20 -0
  65. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.js +1 -1
  66. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.ts +22 -0
  67. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.tsx +23 -0
  68. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.jsx +5 -0
  69. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.tsx +5 -0
  70. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.ts +18 -0
  71. data/lib/react_on_rails/configuration.rb +141 -57
  72. data/lib/react_on_rails/controller.rb +6 -2
  73. data/lib/react_on_rails/dev/file_manager.rb +78 -0
  74. data/lib/react_on_rails/dev/pack_generator.rb +27 -0
  75. data/lib/react_on_rails/dev/process_manager.rb +61 -0
  76. data/lib/react_on_rails/dev/server_manager.rb +487 -0
  77. data/lib/react_on_rails/dev.rb +20 -0
  78. data/lib/react_on_rails/doctor.rb +1149 -0
  79. data/lib/react_on_rails/engine.rb +6 -0
  80. data/lib/react_on_rails/git_utils.rb +12 -2
  81. data/lib/react_on_rails/helper.rb +176 -74
  82. data/lib/react_on_rails/json_parse_error.rb +6 -1
  83. data/lib/react_on_rails/packer_utils.rb +61 -71
  84. data/lib/react_on_rails/packs_generator.rb +221 -19
  85. data/lib/react_on_rails/prerender_error.rb +4 -0
  86. data/lib/react_on_rails/pro/NOTICE +21 -0
  87. data/lib/react_on_rails/pro/helper.rb +122 -0
  88. data/lib/react_on_rails/pro/utils.rb +53 -0
  89. data/lib/react_on_rails/react_component/render_options.rb +38 -6
  90. data/lib/react_on_rails/server_rendering_js_code.rb +0 -1
  91. data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +12 -5
  92. data/lib/react_on_rails/system_checker.rb +659 -0
  93. data/lib/react_on_rails/test_helper/webpack_assets_compiler.rb +1 -1
  94. data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +6 -4
  95. data/lib/react_on_rails/test_helper.rb +2 -3
  96. data/lib/react_on_rails/utils.rb +139 -43
  97. data/lib/react_on_rails/version.rb +1 -1
  98. data/lib/react_on_rails/version_checker.rb +14 -20
  99. data/lib/react_on_rails/version_syntax_converter.rb +1 -1
  100. data/lib/react_on_rails.rb +1 -0
  101. data/lib/tasks/assets.rake +1 -1
  102. data/lib/tasks/doctor.rake +48 -0
  103. data/lib/tasks/generate_packs.rake +158 -1
  104. data/react_on_rails.gemspec +1 -0
  105. data/tsconfig.eslint.json +6 -0
  106. data/tsconfig.json +5 -3
  107. metadata +63 -14
  108. data/REACT-ON-RAILS-PRO-LICENSE +0 -95
  109. data/lib/generators/react_on_rails/adapt_for_older_shakapacker_generator.rb +0 -41
  110. data/lib/generators/react_on_rails/bin/dev +0 -30
  111. data/lib/generators/react_on_rails/bin/dev-static +0 -30
  112. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static.tt +0 -9
  113. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt +0 -5
  114. data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/registration.js.tt +0 -8
  115. /data/lib/generators/react_on_rails/templates/base/base/config/webpack/{webpackConfig.js.tt โ†’ generateWebpackConfigs.js.tt} +0 -0
  116. /data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/{HelloWorldApp.jsx โ†’ HelloWorldApp.client.jsx} +0 -0
@@ -0,0 +1,18 @@
1
+ /* eslint-disable import/prefer-default-export */
2
+
3
+ import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants';
4
+
5
+ // Action interface
6
+ export interface UpdateNameAction {
7
+ type: typeof HELLO_WORLD_NAME_UPDATE;
8
+ text: string;
9
+ }
10
+
11
+ // Union type for all actions
12
+ export type HelloWorldAction = UpdateNameAction;
13
+
14
+ // Action creator with proper TypeScript typing
15
+ export const updateName = (text: string): UpdateNameAction => ({
16
+ type: HELLO_WORLD_NAME_UPDATE,
17
+ text,
18
+ });
@@ -1,4 +1,3 @@
1
- import PropTypes from 'prop-types';
2
1
  import React from 'react';
3
2
  import * as style from './HelloWorld.module.css';
4
3
 
@@ -18,9 +17,4 @@ const HelloWorld = ({ name, updateName }) => (
18
17
  </div>
19
18
  );
20
19
 
21
- HelloWorld.propTypes = {
22
- name: PropTypes.string.isRequired,
23
- updateName: PropTypes.func.isRequired,
24
- };
25
-
26
20
  export default HelloWorld;
@@ -0,0 +1,24 @@
1
+ import React from 'react';
2
+ import * as style from './HelloWorld.module.css';
3
+ import type { PropsFromRedux } from '../containers/HelloWorldContainer';
4
+
5
+ // Component props are inferred from Redux container
6
+ type HelloWorldProps = PropsFromRedux;
7
+
8
+ const HelloWorld: React.FC<HelloWorldProps> = ({ name, updateName }) => (
9
+ <div>
10
+ <h3>
11
+ Hello,
12
+ {name}!
13
+ </h3>
14
+ <hr />
15
+ <form>
16
+ <label className={style.bright} htmlFor="name">
17
+ Say hello to:
18
+ <input id="name" type="text" value={name} onChange={(e) => updateName(e.target.value)} />
19
+ </label>
20
+ </form>
21
+ </div>
22
+ );
23
+
24
+ export default HelloWorld;
@@ -0,0 +1,6 @@
1
+ /* eslint-disable import/prefer-default-export */
2
+
3
+ export const HELLO_WORLD_NAME_UPDATE = 'HELLO_WORLD_NAME_UPDATE' as const;
4
+
5
+ // Action type for TypeScript
6
+ export type HelloWorldActionType = typeof HELLO_WORLD_NAME_UPDATE;
@@ -0,0 +1,20 @@
1
+ // Simple example of a React "smart" component
2
+
3
+ import { connect, ConnectedProps } from 'react-redux';
4
+ import HelloWorld from '../components/HelloWorld';
5
+ import * as actions from '../actions/helloWorldActionCreators';
6
+ import type { HelloWorldState } from '../reducers/helloWorldReducer';
7
+
8
+ // Which part of the Redux global state does our component want to receive as props?
9
+ const mapStateToProps = (state: HelloWorldState) => ({ name: state.name });
10
+
11
+ // Create the connector
12
+ const connector = connect(mapStateToProps, actions);
13
+
14
+ // Infer the props from Redux state and actions
15
+ export type PropsFromRedux = ConnectedProps<typeof connector>;
16
+
17
+ // Don't forget to actually use connect!
18
+ // Note that we don't export HelloWorld, but the redux "connected" version of it.
19
+ // See https://github.com/reactjs/react-redux/blob/master/docs/api.md#examples
20
+ export default connector(HelloWorld);
@@ -1,7 +1,7 @@
1
1
  import { combineReducers } from 'redux';
2
2
  import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants';
3
3
 
4
- const name = (state = '', action) => {
4
+ const name = (state = '', action = {}) => {
5
5
  switch (action.type) {
6
6
  case HELLO_WORLD_NAME_UPDATE:
7
7
  return action.text;
@@ -0,0 +1,22 @@
1
+ import { combineReducers } from 'redux';
2
+ import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants';
3
+ import { HelloWorldAction } from '../actions/helloWorldActionCreators';
4
+
5
+ // State interface
6
+ export interface HelloWorldState {
7
+ name: string;
8
+ }
9
+
10
+ // Individual reducer with TypeScript types
11
+ const name = (state: string = '', action: HelloWorldAction): string => {
12
+ switch (action.type) {
13
+ case HELLO_WORLD_NAME_UPDATE:
14
+ return action.text;
15
+ default:
16
+ return state;
17
+ }
18
+ };
19
+
20
+ const helloWorldReducer = combineReducers<HelloWorldState>({ name });
21
+
22
+ export default helloWorldReducer;
@@ -0,0 +1,23 @@
1
+ import { useMemo, type FC } from 'react';
2
+ import { Provider } from 'react-redux';
3
+
4
+ import configureStore, { type RailsProps } from '../store/helloWorldStore';
5
+ import HelloWorldContainer from '../containers/HelloWorldContainer';
6
+
7
+ // Props interface matches what Rails will pass from the controller
8
+ interface HelloWorldAppProps extends RailsProps {}
9
+
10
+ // See documentation for https://github.com/reactjs/react-redux.
11
+ // This is how you get props from the Rails view into the redux store.
12
+ // This code here binds your smart component to the redux store.
13
+ const HelloWorldApp: FC<HelloWorldAppProps> = (props) => {
14
+ const store = useMemo(() => configureStore(props), [props]);
15
+
16
+ return (
17
+ <Provider store={store}>
18
+ <HelloWorldContainer />
19
+ </Provider>
20
+ );
21
+ };
22
+
23
+ export default HelloWorldApp;
@@ -0,0 +1,5 @@
1
+ import HelloWorldApp from './HelloWorldApp.client';
2
+ // This could be specialized for server rendering
3
+ // For example, if using React Router, we'd have the SSR setup here.
4
+
5
+ export default HelloWorldApp;
@@ -0,0 +1,5 @@
1
+ import HelloWorldApp from './HelloWorldApp.client';
2
+ // This could be specialized for server rendering
3
+ // For example, if using React Router, we'd have the SSR setup here.
4
+
5
+ export default HelloWorldApp;
@@ -0,0 +1,18 @@
1
+ import { createStore } from 'redux';
2
+ import type { Store, PreloadedState } from 'redux';
3
+ import helloWorldReducer from '../reducers/helloWorldReducer';
4
+ import type { HelloWorldState } from '../reducers/helloWorldReducer';
5
+
6
+ // Rails props interface - customize based on your Rails controller
7
+ export interface RailsProps {
8
+ name: string;
9
+ [key: string]: any; // Allow additional props from Rails
10
+ }
11
+
12
+ // Store type
13
+ export type HelloWorldStore = Store<HelloWorldState>;
14
+
15
+ const configureStore = (railsProps: RailsProps): HelloWorldStore =>
16
+ createStore(helloWorldReducer, railsProps as PreloadedState<HelloWorldState>);
17
+
18
+ export default configureStore;
@@ -9,6 +9,9 @@ module ReactOnRails
9
9
  end
10
10
 
11
11
  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
+ DEFAULT_COMPONENT_REGISTRY_TIMEOUT = 5000
12
15
 
13
16
  def self.configuration
14
17
  @configuration ||= Configuration.new(
@@ -17,6 +20,9 @@ module ReactOnRails
17
20
  # generated_assets_dirs is deprecated
18
21
  generated_assets_dir: "",
19
22
  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,
20
26
  prerender: false,
21
27
  auto_load_bundle: false,
22
28
  replay_console: true,
@@ -39,10 +45,20 @@ module ReactOnRails
39
45
  i18n_output_format: nil,
40
46
  components_subdirectory: nil,
41
47
  make_generated_server_bundle_the_entrypoint: false,
42
- defer_generated_component_packs: true,
43
- # forces the loading of React components
44
- force_load: false
48
+ defer_generated_component_packs: false,
49
+ # React on Rails Pro (licensed) feature - enables immediate hydration of React components
50
+ immediate_hydration: false,
51
+ # Maximum time in milliseconds to wait for client-side component registration after page load.
52
+ # If exceeded, an error will be thrown for server-side rendered components not registered on the client.
53
+ # Set to 0 to disable the timeout and wait indefinitely for component registration.
54
+ component_registry_timeout: DEFAULT_COMPONENT_REGISTRY_TIMEOUT,
55
+ generated_component_packs_loading_strategy: nil,
56
+ server_bundle_output_path: "ssr-generated",
57
+ enforce_private_server_bundles: false
45
58
  )
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
46
62
  end
47
63
 
48
64
  class Configuration
@@ -52,27 +68,30 @@ module ReactOnRails
52
68
  :generated_assets_dirs, :generated_assets_dir, :components_subdirectory,
53
69
  :webpack_generated_files, :rendering_extension, :build_test_command,
54
70
  :build_production_command, :i18n_dir, :i18n_yml_dir, :i18n_output_format,
55
- :i18n_yml_safe_load_options,
71
+ :i18n_yml_safe_load_options, :defer_generated_component_packs,
56
72
  :server_render_method, :random_dom_id, :auto_load_bundle,
57
73
  :same_bundle_for_client_and_server, :rendering_props_extension,
58
74
  :make_generated_server_bundle_the_entrypoint,
59
- :defer_generated_component_packs,
60
- :force_load
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,
77
+ :server_bundle_output_path, :enforce_private_server_bundles
61
78
 
62
79
  # rubocop:disable Metrics/AbcSize
63
80
  def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender: nil,
64
81
  replay_console: nil, make_generated_server_bundle_the_entrypoint: nil,
65
- trace: nil, development_mode: nil,
82
+ trace: nil, development_mode: nil, defer_generated_component_packs: nil,
66
83
  logging_on_server: nil, server_renderer_pool_size: nil,
67
84
  server_renderer_timeout: nil, raise_on_prerender_error: true,
68
85
  skip_display_none: nil, generated_assets_dirs: nil,
69
86
  generated_assets_dir: nil, webpack_generated_files: nil,
70
87
  rendering_extension: nil, build_test_command: nil,
71
- build_production_command: nil, defer_generated_component_packs: nil,
88
+ build_production_command: nil, generated_component_packs_loading_strategy: nil,
72
89
  same_bundle_for_client_and_server: nil,
73
90
  i18n_dir: nil, i18n_yml_dir: nil, i18n_output_format: nil, i18n_yml_safe_load_options: nil,
74
91
  random_dom_id: nil, server_render_method: nil, rendering_props_extension: nil,
75
- components_subdirectory: nil, auto_load_bundle: nil, force_load: 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,
94
+ component_registry_timeout: nil, server_bundle_output_path: nil, enforce_private_server_bundles: nil)
76
95
  self.node_modules_location = node_modules_location.present? ? node_modules_location : Rails.root
77
96
  self.generated_assets_dirs = generated_assets_dirs
78
97
  self.generated_assets_dir = generated_assets_dir
@@ -96,9 +115,13 @@ module ReactOnRails
96
115
  self.raise_on_prerender_error = raise_on_prerender_error
97
116
  self.skip_display_none = skip_display_none
98
117
  self.rendering_props_extension = rendering_props_extension
118
+ self.component_registry_timeout = component_registry_timeout
99
119
 
100
120
  # Server rendering:
101
121
  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
102
125
  self.same_bundle_for_client_and_server = same_bundle_for_client_and_server
103
126
  self.server_renderer_pool_size = self.development_mode ? 1 : server_renderer_pool_size
104
127
  self.server_renderer_timeout = server_renderer_timeout # seconds
@@ -111,7 +134,10 @@ module ReactOnRails
111
134
  self.auto_load_bundle = auto_load_bundle
112
135
  self.make_generated_server_bundle_the_entrypoint = make_generated_server_bundle_the_entrypoint
113
136
  self.defer_generated_component_packs = defer_generated_component_packs
114
- self.force_load = force_load
137
+ self.immediate_hydration = immediate_hydration
138
+ self.generated_component_packs_loading_strategy = generated_component_packs_loading_strategy
139
+ self.server_bundle_output_path = server_bundle_output_path
140
+ self.enforce_private_server_bundles = enforce_private_server_bundles
115
141
  end
116
142
  # rubocop:enable Metrics/AbcSize
117
143
 
@@ -121,24 +147,95 @@ module ReactOnRails
121
147
  ensure_webpack_generated_files_exists
122
148
  configure_generated_assets_dirs_deprecation
123
149
  configure_skip_display_none_deprecation
124
- ensure_generated_assets_dir_present
125
150
  check_server_render_method_is_only_execjs
126
151
  error_if_using_packer_and_generated_assets_dir_not_match_public_output_path
127
152
  # check_deprecated_settings
128
153
  adjust_precompile_task
154
+ check_component_registry_timeout
155
+ validate_generated_component_packs_loading_strategy
156
+ validate_enforce_private_server_bundles
129
157
  end
130
158
 
131
159
  private
132
160
 
161
+ def check_component_registry_timeout
162
+ self.component_registry_timeout = DEFAULT_COMPONENT_REGISTRY_TIMEOUT if component_registry_timeout.nil?
163
+
164
+ return if component_registry_timeout.is_a?(Integer) && component_registry_timeout >= 0
165
+
166
+ raise ReactOnRails::Error, "component_registry_timeout must be a positive integer"
167
+ end
168
+
169
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
170
+ def validate_generated_component_packs_loading_strategy
171
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
172
+
173
+ if defer_generated_component_packs
174
+ if %i[async sync].include?(generated_component_packs_loading_strategy)
175
+ Rails.logger.warn "**WARNING** ReactOnRails: config.defer_generated_component_packs is " \
176
+ "superseded by config.generated_component_packs_loading_strategy"
177
+ else
178
+ Rails.logger.warn "[DEPRECATION] ReactOnRails: Use config." \
179
+ "generated_component_packs_loading_strategy = :defer rather than " \
180
+ "defer_generated_component_packs"
181
+ self.generated_component_packs_loading_strategy ||= :defer
182
+ end
183
+ end
184
+
185
+ msg = <<~MSG
186
+ 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
189
+ 2. Upgrade to Shakapacker v8.2.0 or above to enable async script loading
190
+ MSG
191
+ if PackerUtils.supports_async_loading?
192
+ self.generated_component_packs_loading_strategy ||= :async
193
+ elsif generated_component_packs_loading_strategy.nil?
194
+ Rails.logger.warn("**WARNING** #{msg}")
195
+ self.generated_component_packs_loading_strategy = :sync
196
+ elsif generated_component_packs_loading_strategy == :async
197
+ raise ReactOnRails::Error, "**ERROR** #{msg}"
198
+ end
199
+
200
+ return if %i[async defer sync].include?(generated_component_packs_loading_strategy)
201
+
202
+ raise ReactOnRails::Error, "generated_component_packs_loading_strategy must be either :async, :defer, or :sync"
203
+ end
204
+
205
+ def validate_enforce_private_server_bundles
206
+ return unless enforce_private_server_bundles
207
+
208
+ # Check if server_bundle_output_path is nil
209
+ if server_bundle_output_path.nil?
210
+ raise ReactOnRails::Error, "enforce_private_server_bundles is set to true, but " \
211
+ "server_bundle_output_path is nil. Please set server_bundle_output_path " \
212
+ "to a directory outside of the public directory."
213
+ end
214
+
215
+ # Check if server_bundle_output_path is inside public directory
216
+ # Skip validation if Rails.root is not available (e.g., in tests)
217
+ return unless defined?(Rails) && Rails.root
218
+
219
+ public_path = Rails.root.join("public").to_s
220
+ server_output_path = File.expand_path(server_bundle_output_path, Rails.root.to_s)
221
+
222
+ return unless server_output_path.start_with?(public_path)
223
+
224
+ raise ReactOnRails::Error, "enforce_private_server_bundles is set to true, but " \
225
+ "server_bundle_output_path (#{server_bundle_output_path}) is inside " \
226
+ "the public directory. Please set it to a directory outside of public."
227
+ end
228
+
229
+ def check_minimum_shakapacker_version
230
+ ReactOnRails::PackerUtils.raise_shakapacker_version_incompatible_for_basic_pack_generation unless
231
+ ReactOnRails::PackerUtils.supports_basic_pack_generation?
232
+ end
233
+
133
234
  def check_autobundling_requirements
134
- raise_missing_components_subdirectory if auto_load_bundle && !components_subdirectory.present?
135
- return unless components_subdirectory.present?
235
+ raise_missing_components_subdirectory unless components_subdirectory.present?
136
236
 
137
- ReactOnRails::PackerUtils.raise_shakapacker_not_installed unless ReactOnRails::PackerUtils.using_packer?
138
237
  ReactOnRails::PackerUtils.raise_shakapacker_version_incompatible_for_autobundling unless
139
- ReactOnRails::PackerUtils.shakapacker_version_requirement_met?(
140
- ReactOnRails::PacksGenerator::MINIMUM_SHAKAPACKER_VERSION
141
- )
238
+ ReactOnRails::PackerUtils.supports_autobundling?
142
239
  ReactOnRails::PackerUtils.raise_nested_entries_disabled unless ReactOnRails::PackerUtils.nested_entries?
143
240
  end
144
241
 
@@ -157,8 +254,8 @@ module ReactOnRails
157
254
  # We set it very big so that it is not used, and then clean just
158
255
  # removes files older than 1 hour.
159
256
  versions = 100_000
160
- puts "Invoking task #{ReactOnRails::PackerUtils.packer_type}:clean from React on Rails"
161
- Rake::Task["#{ReactOnRails::PackerUtils.packer_type}:clean"].invoke(versions)
257
+ puts "Invoking task shakapacker:clean from React on Rails"
258
+ Rake::Task["shakapacker:clean"].invoke(versions)
162
259
  }
163
260
 
164
261
  if Rake::Task.task_defined?("assets:precompile")
@@ -173,22 +270,20 @@ module ReactOnRails
173
270
  end
174
271
 
175
272
  def error_if_using_packer_and_generated_assets_dir_not_match_public_output_path
176
- return unless ReactOnRails::PackerUtils.using_packer?
177
-
178
273
  return if generated_assets_dir.blank?
179
274
 
180
275
  packer_public_output_path = ReactOnRails::PackerUtils.packer_public_output_path
181
276
 
182
277
  if File.expand_path(generated_assets_dir) == packer_public_output_path.to_s
183
278
  Rails.logger.warn("You specified generated_assets_dir in `config/initializers/react_on_rails.rb` " \
184
- "with #{ReactOnRails::PackerUtils.packer_type}. " \
279
+ "with Shakapacker. " \
185
280
  "Remove this line from your configuration file.")
186
281
  else
187
282
  msg = <<~MSG
188
- Error configuring /config/initializers/react_on_rails.rb: You are using #{ReactOnRails::PackerUtils.packer_type}
283
+ Error configuring /config/initializers/react_on_rails.rb: You are using Shakapacker
189
284
  and your specified value for generated_assets_dir = #{generated_assets_dir}
190
285
  that does not match the value for public_output_path specified in
191
- #{ReactOnRails::PackerUtils.packer_type}.yml = #{packer_public_output_path}. You should remove the configuration
286
+ shakapacker.yml = #{packer_public_output_path}. You should remove the configuration
192
287
  value for "generated_assets_dir" from your config/initializers/react_on_rails.rb file.
193
288
  MSG
194
289
  raise ReactOnRails::Error, msg
@@ -207,44 +302,33 @@ module ReactOnRails
207
302
  raise ReactOnRails::Error, msg
208
303
  end
209
304
 
210
- def ensure_generated_assets_dir_present
211
- return if generated_assets_dir.present? || ReactOnRails::PackerUtils.using_packer?
212
-
213
- self.generated_assets_dir = DEFAULT_GENERATED_ASSETS_DIR
214
- Rails.logger.warn "ReactOnRails: Set generated_assets_dir to default: #{DEFAULT_GENERATED_ASSETS_DIR}"
215
- end
216
-
217
305
  def configure_generated_assets_dirs_deprecation
218
306
  return if generated_assets_dirs.blank?
219
307
 
220
- if ReactOnRails::PackerUtils.using_packer?
221
- packer_public_output_path = ReactOnRails::PackerUtils.packer_public_output_path
222
- # rubocop:disable Layout/LineLength
223
- Rails.logger.warn "Error configuring config/initializers/react_on_rails. Define neither the generated_assets_dirs nor " \
224
- "the generated_assets_dir when using #{ReactOnRails::PackerUtils.packer_type.upcase_first}. This is defined by " \
225
- "public_output_path specified in #{ReactOnRails::PackerUtils.packer_type}.yml = #{packer_public_output_path}."
226
- # rubocop:enable Layout/LineLength
227
- return
228
- end
308
+ packer_public_output_path = ReactOnRails::PackerUtils.packer_public_output_path
229
309
 
230
- Rails.logger.warn "[DEPRECATION] ReactOnRails: Use config.generated_assets_dir rather than " \
231
- "generated_assets_dirs"
232
- if generated_assets_dir.blank?
233
- self.generated_assets_dir = generated_assets_dirs
234
- else
235
- Rails.logger.warn "[DEPRECATION] ReactOnRails. You have both generated_assets_dirs and " \
236
- "generated_assets_dir defined. Define ONLY generated_assets_dir if NOT using Shakapacker " \
237
- "and define neither if using Webpacker"
238
- end
310
+ msg = <<~MSG
311
+ ReactOnRails Configuration Warning: The 'generated_assets_dirs' configuration option is no longer supported.
312
+ Since Shakapacker is now required, public asset paths are automatically determined from your shakapacker.yml configuration.
313
+ Please remove 'config.generated_assets_dirs' from your config/initializers/react_on_rails.rb file.
314
+ Public assets will be loaded from: #{packer_public_output_path}
315
+ If you need to customize the public output path, configure it in config/shakapacker.yml under 'public_output_path'.
316
+ Note: Private server bundles are configured separately via server_bundle_output_path.
317
+ MSG
318
+
319
+ Rails.logger.warn msg
239
320
  end
240
321
 
241
322
  def ensure_webpack_generated_files_exists
242
323
  return unless webpack_generated_files.empty?
243
324
 
244
- files = ["manifest.json"]
245
- files << server_bundle_js_file if server_bundle_js_file.present?
246
-
247
- self.webpack_generated_files = files
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
248
332
  end
249
333
 
250
334
  def configure_skip_display_none_deprecation
@@ -266,13 +350,13 @@ module ReactOnRails
266
350
  def compile_command_conflict_message
267
351
  <<~MSG
268
352
 
269
- React on Rails and #{ReactOnRails::PackerUtils.packer_type.upcase_first} error in configuration!
353
+ React on Rails and Shakapacker error in configuration!
270
354
  In order to use config/react_on_rails.rb config.build_production_command,
271
- you must edit config/#{ReactOnRails::PackerUtils.packer_type}.yml to include this value in the default configuration:
272
- '#{ReactOnRails::PackerUtils.packer_type}_precompile: false'
355
+ you must edit config/shakapacker.yml to include this value in the default configuration:
356
+ 'shakapacker_precompile: false'
273
357
 
274
358
  Alternatively, remove the config/react_on_rails.rb config.build_production_command and the
275
- default bin/#{ReactOnRails::PackerUtils.packer_type} script will be used for assets:precompile.
359
+ default bin/shakapacker script will be used for assets:precompile.
276
360
 
277
361
  MSG
278
362
  end
@@ -9,12 +9,16 @@ 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
14
  #
13
15
  # Be sure to include view helper `redux_store_hydration_data` at the end of your layout or view
14
16
  # or else there will be no client side hydration of your stores.
15
- def redux_store(store_name, props: {})
17
+ def redux_store(store_name, props: {}, immediate_hydration: nil)
18
+ immediate_hydration = ReactOnRails.configuration.immediate_hydration if immediate_hydration.nil?
16
19
  redux_store_data = { store_name: store_name,
17
- props: props }
20
+ props: props,
21
+ immediate_hydration: immediate_hydration }
18
22
  @registered_stores_defer_render ||= []
19
23
  @registered_stores_defer_render << redux_store_data
20
24
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ReactOnRails
4
+ module Dev
5
+ class FileManager
6
+ class << self
7
+ def cleanup_stale_files
8
+ socket_cleanup = cleanup_overmind_sockets
9
+ pid_cleanup = cleanup_rails_pid_file
10
+
11
+ socket_cleanup || pid_cleanup
12
+ end
13
+
14
+ private
15
+
16
+ def cleanup_overmind_sockets
17
+ return false if overmind_running?
18
+
19
+ socket_files = [".overmind.sock", "tmp/sockets/overmind.sock"]
20
+ cleaned_any = false
21
+
22
+ socket_files.each do |socket_file|
23
+ cleaned_any = true if remove_file_if_exists(socket_file, "stale socket")
24
+ end
25
+
26
+ cleaned_any
27
+ end
28
+
29
+ def cleanup_rails_pid_file
30
+ server_pid_file = "tmp/pids/server.pid"
31
+ return false unless File.exist?(server_pid_file)
32
+
33
+ pid_content = File.read(server_pid_file).strip
34
+ begin
35
+ pid = Integer(pid_content)
36
+ # PIDs must be > 1 (0 is kernel, 1 is init)
37
+ if pid <= 1
38
+ remove_file_if_exists(server_pid_file, "stale Rails pid file")
39
+ return true
40
+ end
41
+ rescue ArgumentError, TypeError
42
+ remove_file_if_exists(server_pid_file, "stale Rails pid file")
43
+ return true
44
+ end
45
+
46
+ return false if process_running?(pid)
47
+
48
+ remove_file_if_exists(server_pid_file, "stale Rails pid file")
49
+ end
50
+
51
+ def overmind_running?
52
+ !`pgrep -f "overmind" 2>/dev/null`.split("\n").empty?
53
+ end
54
+
55
+ def process_running?(pid)
56
+ Process.kill(0, pid)
57
+ true
58
+ rescue Errno::ESRCH, ArgumentError, RangeError
59
+ # Process doesn't exist or invalid PID
60
+ false
61
+ rescue Errno::EPERM
62
+ # Process exists but we don't have permission to signal it
63
+ true
64
+ end
65
+
66
+ def remove_file_if_exists(file_path, description)
67
+ return false unless File.exist?(file_path)
68
+
69
+ puts " ๐Ÿงน Cleaning up #{description}: #{file_path}"
70
+ File.delete(file_path)
71
+ true
72
+ rescue StandardError
73
+ false
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "English"
4
+
5
+ module ReactOnRails
6
+ module Dev
7
+ class PackGenerator
8
+ class << self
9
+ def generate(verbose: false)
10
+ if verbose
11
+ puts "๐Ÿ“ฆ Generating React on Rails packs..."
12
+ success = system "bundle exec rake react_on_rails:generate_packs"
13
+ else
14
+ print "๐Ÿ“ฆ Generating packs... "
15
+ success = system "bundle exec rake react_on_rails:generate_packs > /dev/null 2>&1"
16
+ puts success ? "โœ…" : "โŒ"
17
+ end
18
+
19
+ return if success
20
+
21
+ puts "โŒ Pack generation failed"
22
+ exit 1
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end