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.
- checksums.yaml +4 -4
- data/AI_AGENT_INSTRUCTIONS.md +63 -0
- data/CHANGELOG.md +564 -85
- data/CLAUDE.md +135 -0
- data/CODING_AGENTS.md +313 -0
- data/CONTRIBUTING.md +448 -37
- data/Gemfile.development_dependencies +6 -1
- data/Gemfile.lock +13 -4
- data/KUDOS.md +22 -1
- data/LICENSE.md +30 -4
- data/LICENSES/README.md +14 -0
- data/NEWS.md +48 -48
- data/PROJECTS.md +45 -40
- data/REACT-ON-RAILS-PRO-LICENSE.md +129 -0
- data/README.md +113 -42
- data/SUMMARY.md +62 -52
- data/TODO.md +135 -0
- data/bin/lefthook/check-trailing-newlines +38 -0
- data/bin/lefthook/get-changed-files +26 -0
- data/bin/lefthook/prettier-format +26 -0
- data/bin/lefthook/ruby-autofix +26 -0
- data/bin/lefthook/ruby-lint +27 -0
- data/eslint.config.ts +232 -0
- data/knip.ts +40 -6
- data/lib/generators/USAGE +4 -5
- data/lib/generators/react_on_rails/USAGE +65 -0
- data/lib/generators/react_on_rails/base_generator.rb +276 -62
- data/lib/generators/react_on_rails/dev_tests_generator.rb +1 -0
- data/lib/generators/react_on_rails/generator_helper.rb +35 -1
- data/lib/generators/react_on_rails/generator_messages.rb +138 -17
- data/lib/generators/react_on_rails/install_generator.rb +474 -26
- data/lib/generators/react_on_rails/react_no_redux_generator.rb +19 -6
- data/lib/generators/react_on_rails/react_with_redux_generator.rb +110 -18
- data/lib/generators/react_on_rails/templates/.eslintrc +1 -1
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev +5 -0
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-prod-assets +8 -0
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static-assets +2 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -5
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +2 -2
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorldServer.js +1 -1
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/server-bundle.js +1 -8
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx +21 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.tsx +25 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css +4 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.jsx +5 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.tsx +5 -0
- data/lib/generators/react_on_rails/templates/base/base/app/views/hello_world/index.html.erb.tt +1 -1
- data/lib/generators/react_on_rails/templates/base/base/app/views/layouts/hello_world.html.erb +4 -2
- data/lib/generators/react_on_rails/templates/base/base/babel.config.js.tt +5 -2
- data/lib/generators/react_on_rails/templates/base/base/bin/dev +34 -0
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +14 -5
- data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +76 -7
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +1 -1
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +6 -10
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/production.js.tt +2 -2
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +3 -2
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/test.js.tt +2 -2
- data/lib/generators/react_on_rails/templates/dev_tests/spec/system/hello_world_spec.rb +0 -2
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.ts +18 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -6
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +4 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx +24 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/constants/helloWorldConstants.ts +6 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/containers/HelloWorldContainer.ts +20 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.js +1 -1
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.ts +22 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.tsx +23 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.jsx +5 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.tsx +5 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.ts +18 -0
- data/lib/react_on_rails/configuration.rb +141 -57
- data/lib/react_on_rails/controller.rb +6 -2
- data/lib/react_on_rails/dev/file_manager.rb +78 -0
- data/lib/react_on_rails/dev/pack_generator.rb +27 -0
- data/lib/react_on_rails/dev/process_manager.rb +61 -0
- data/lib/react_on_rails/dev/server_manager.rb +487 -0
- data/lib/react_on_rails/dev.rb +20 -0
- data/lib/react_on_rails/doctor.rb +1149 -0
- data/lib/react_on_rails/engine.rb +6 -0
- data/lib/react_on_rails/git_utils.rb +12 -2
- data/lib/react_on_rails/helper.rb +176 -74
- data/lib/react_on_rails/json_parse_error.rb +6 -1
- data/lib/react_on_rails/packer_utils.rb +61 -71
- data/lib/react_on_rails/packs_generator.rb +221 -19
- data/lib/react_on_rails/prerender_error.rb +4 -0
- data/lib/react_on_rails/pro/NOTICE +21 -0
- data/lib/react_on_rails/pro/helper.rb +122 -0
- data/lib/react_on_rails/pro/utils.rb +53 -0
- data/lib/react_on_rails/react_component/render_options.rb +38 -6
- data/lib/react_on_rails/server_rendering_js_code.rb +0 -1
- data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +12 -5
- data/lib/react_on_rails/system_checker.rb +659 -0
- data/lib/react_on_rails/test_helper/webpack_assets_compiler.rb +1 -1
- data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +6 -4
- data/lib/react_on_rails/test_helper.rb +2 -3
- data/lib/react_on_rails/utils.rb +139 -43
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_checker.rb +14 -20
- data/lib/react_on_rails/version_syntax_converter.rb +1 -1
- data/lib/react_on_rails.rb +1 -0
- data/lib/tasks/assets.rake +1 -1
- data/lib/tasks/doctor.rake +48 -0
- data/lib/tasks/generate_packs.rake +158 -1
- data/react_on_rails.gemspec +1 -0
- data/tsconfig.eslint.json +6 -0
- data/tsconfig.json +5 -3
- metadata +63 -14
- data/REACT-ON-RAILS-PRO-LICENSE +0 -95
- data/lib/generators/react_on_rails/adapt_for_older_shakapacker_generator.rb +0 -41
- data/lib/generators/react_on_rails/bin/dev +0 -30
- data/lib/generators/react_on_rails/bin/dev-static +0 -30
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static.tt +0 -9
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt +0 -5
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/registration.js.tt +0 -8
- /data/lib/generators/react_on_rails/templates/base/base/config/webpack/{webpackConfig.js.tt โ generateWebpackConfigs.js.tt} +0 -0
- /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,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,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:
|
43
|
-
#
|
44
|
-
|
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
|
-
:
|
60
|
-
:
|
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,
|
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,
|
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.
|
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
|
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.
|
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
|
161
|
-
Rake::Task["
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
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
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
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
|
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
|
272
|
-
'
|
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
|
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
|