react_on_rails 16.4.0.rc.3 → 16.4.0.rc.5
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/Gemfile.lock +1 -1
- data/lib/generators/react_on_rails/base_generator.rb +6 -5
- data/lib/generators/react_on_rails/generator_messages.rb +17 -3
- data/lib/generators/react_on_rails/install_generator.rb +2 -1
- data/lib/generators/react_on_rails/templates/rsc/base/app/controllers/hello_server_controller.rb +2 -0
- data/lib/generators/react_on_rails/templates/rsc/base/config/webpack/rscWebpackConfig.js.tt +29 -10
- data/lib/react_on_rails/packs_generator.rb +44 -0
- data/lib/react_on_rails/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 122f40da16cc6a79e95e0679394667388c9cae337f19be2f61db4463a221a572
|
|
4
|
+
data.tar.gz: 38213ba983ecc6d2e6ea065de078a93305e6aaed6419069d5c94e427b3cef507
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 82b8e04feec8ea4f73a8fc821adaa5343ce6fe4cf7e932c5b77711a2c0036b9b2d713834d4e900d530f310b25d2fcb746492e279615427c188e8b89e7f1a29c6
|
|
7
|
+
data.tar.gz: 0d06abde7b925466f835ec2646e9969348adbdc8cb8761a5995668ab0b607ec7a79621a6461553e4511a8cc654fb8b33d388b8e3bcc0ab331f93bf3c2d723e57
|
data/Gemfile.lock
CHANGED
|
@@ -61,12 +61,13 @@ module ReactOnRails
|
|
|
61
61
|
.dev-services.yml.example
|
|
62
62
|
bin/shakapacker-precompile-hook]
|
|
63
63
|
|
|
64
|
-
#
|
|
64
|
+
# HelloServer uses the hello_world layout so React on Rails can inject generated
|
|
65
|
+
# packs without requiring a hardcoded application.js pack entry.
|
|
66
|
+
base_files << "app/views/layouts/hello_world.html.erb"
|
|
67
|
+
|
|
68
|
+
# HelloWorld controller only when not using RSC (RSC uses HelloServer)
|
|
65
69
|
# Exception: Redux still needs the HelloWorld controller even with RSC
|
|
66
|
-
unless use_rsc? && !options.redux?
|
|
67
|
-
base_files += %w[app/controllers/hello_world_controller.rb
|
|
68
|
-
app/views/layouts/hello_world.html.erb]
|
|
69
|
-
end
|
|
70
|
+
base_files << "app/controllers/hello_world_controller.rb" unless use_rsc? && !options.redux?
|
|
70
71
|
base_templates = %w[config/initializers/react_on_rails.rb]
|
|
71
72
|
base_files.each { |file| copy_file("#{base_path}#{file}", file) }
|
|
72
73
|
base_templates.each do |file|
|
|
@@ -38,11 +38,17 @@ module GeneratorMessages
|
|
|
38
38
|
@output = []
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def helpful_message_after_installation(component_name: "HelloWorld", route: "hello_world")
|
|
41
|
+
def helpful_message_after_installation(component_name: "HelloWorld", route: "hello_world", rsc: false)
|
|
42
42
|
process_manager_section = build_process_manager_section
|
|
43
43
|
testing_section = build_testing_section
|
|
44
44
|
package_manager = detect_package_manager
|
|
45
45
|
shakapacker_status = build_shakapacker_status_section
|
|
46
|
+
render_example = build_render_example(component_name: component_name, route: route, rsc: rsc)
|
|
47
|
+
render_label = if rsc
|
|
48
|
+
"• Streaming server rendering in app/views/#{route}/index.html.erb:"
|
|
49
|
+
else
|
|
50
|
+
"• Server-side rendering - Enabled with prerender option in app/views/#{route}/index.html.erb:"
|
|
51
|
+
end
|
|
46
52
|
|
|
47
53
|
<<~MSG
|
|
48
54
|
|
|
@@ -69,8 +75,8 @@ module GeneratorMessages
|
|
|
69
75
|
<%= javascript_pack_tag %>
|
|
70
76
|
<%= stylesheet_pack_tag %>
|
|
71
77
|
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
#{render_label}
|
|
79
|
+
#{render_example}
|
|
74
80
|
|
|
75
81
|
📚 LEARN MORE:
|
|
76
82
|
─────────────────────────────────────────────────────────────────────────
|
|
@@ -83,6 +89,14 @@ module GeneratorMessages
|
|
|
83
89
|
|
|
84
90
|
private
|
|
85
91
|
|
|
92
|
+
def build_render_example(component_name:, route:, rsc:)
|
|
93
|
+
if rsc
|
|
94
|
+
"<%= stream_react_component(\"#{component_name}\", props: @#{route}_props) %>"
|
|
95
|
+
else
|
|
96
|
+
"<%= react_component(\"#{component_name}\", props: @#{route}_props, prerender: true) %>"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
86
100
|
def build_process_manager_section
|
|
87
101
|
process_manager = detect_process_manager
|
|
88
102
|
if process_manager
|
|
@@ -8,13 +8,15 @@ const serverWebpackModule = require('./serverWebpackConfig');
|
|
|
8
8
|
// - New Pro config exports: { default: configureServer, extractLoader }
|
|
9
9
|
// - Legacy config exports: module.exports = configureServer
|
|
10
10
|
const serverWebpackConfig = serverWebpackModule.default || serverWebpackModule;
|
|
11
|
-
const extractLoader =
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
return
|
|
11
|
+
const extractLoader =
|
|
12
|
+
serverWebpackModule.extractLoader ||
|
|
13
|
+
((rule, loaderName) => {
|
|
14
|
+
if (!Array.isArray(rule.use)) return null;
|
|
15
|
+
return rule.use.find((item) => {
|
|
16
|
+
const testValue = typeof item === 'string' ? item : item.loader;
|
|
17
|
+
return testValue && testValue.includes(loaderName);
|
|
18
|
+
});
|
|
16
19
|
});
|
|
17
|
-
});
|
|
18
20
|
|
|
19
21
|
const configureRsc = () => {
|
|
20
22
|
// Pass true to skip RSCWebpackPlugin - RSC bundle doesn't need it
|
|
@@ -26,12 +28,29 @@ const configureRsc = () => {
|
|
|
26
28
|
};
|
|
27
29
|
rscConfig.entry = rscEntry;
|
|
28
30
|
|
|
29
|
-
// Add the RSC
|
|
30
|
-
// This loader
|
|
31
|
+
// Add the RSC WebpackLoader to the JS rule's loader chain.
|
|
32
|
+
// This loader replaces 'use client' files with registerClientReference proxies in the RSC bundle.
|
|
33
|
+
// Webpack loaders execute right-to-left, so appending makes the RSC loader run first (before babel/swc).
|
|
31
34
|
const { rules } = rscConfig.module;
|
|
32
35
|
rules.forEach((rule) => {
|
|
33
|
-
if (
|
|
34
|
-
//
|
|
36
|
+
if (typeof rule.use === 'function') {
|
|
37
|
+
// SWC transpiler defines rule.use as a function: use: ({resource}) => getSwcLoaderConfig(resource)
|
|
38
|
+
// Wrap it to append the RSC WebpackLoader to the returned loader(s).
|
|
39
|
+
const originalUse = rule.use;
|
|
40
|
+
// Must use `function` (not arrow) so `.call(this, data)` forwards webpack's context.
|
|
41
|
+
rule.use = function rscLoaderWrapper(data) {
|
|
42
|
+
const result = originalUse.call(this, data);
|
|
43
|
+
const resultArray = Array.isArray(result) ? result : result ? [result] : [];
|
|
44
|
+
const resolvedRule = { use: resultArray };
|
|
45
|
+
const jsLoader =
|
|
46
|
+
extractLoader(resolvedRule, 'babel-loader') || extractLoader(resolvedRule, 'swc-loader');
|
|
47
|
+
if (jsLoader) {
|
|
48
|
+
return [...resultArray, { loader: 'react-on-rails-rsc/WebpackLoader' }];
|
|
49
|
+
}
|
|
50
|
+
return result;
|
|
51
|
+
};
|
|
52
|
+
} else if (Array.isArray(rule.use)) {
|
|
53
|
+
// Babel transpiler defines rule.use as a static array
|
|
35
54
|
const jsLoader = extractLoader(rule, 'babel-loader') || extractLoader(rule, 'swc-loader');
|
|
36
55
|
if (jsLoader) {
|
|
37
56
|
rule.use.push({
|
|
@@ -58,6 +58,29 @@ module ReactOnRails
|
|
|
58
58
|
store_to_path.each_value { |store_path| create_store_pack(store_path, verbose: verbose) }
|
|
59
59
|
|
|
60
60
|
create_server_pack(verbose: verbose) if ReactOnRails.configuration.server_bundle_js_file.present?
|
|
61
|
+
|
|
62
|
+
log_rsc_classification_summary if ReactOnRails::Utils.rsc_support_enabled?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def log_rsc_classification_summary
|
|
66
|
+
all_components = common_component_to_path.merge(client_component_to_path)
|
|
67
|
+
server = []
|
|
68
|
+
client = []
|
|
69
|
+
|
|
70
|
+
all_components.each do |name, path|
|
|
71
|
+
if client_entrypoint?(path)
|
|
72
|
+
client << name
|
|
73
|
+
else
|
|
74
|
+
server << name
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
return if server.empty? && client.empty?
|
|
79
|
+
|
|
80
|
+
summary = +"[react_on_rails] RSC component classification:\n"
|
|
81
|
+
summary << " Server components (no 'use client'): #{server.any? ? server.join(', ') : '(none)'}\n"
|
|
82
|
+
summary << " Client components ('use client' found): #{client.any? ? client.join(', ') : '(none)'}"
|
|
83
|
+
puts Rainbow(summary).cyan
|
|
61
84
|
end
|
|
62
85
|
|
|
63
86
|
def check_for_component_store_name_conflicts
|
|
@@ -128,11 +151,32 @@ module ReactOnRails
|
|
|
128
151
|
first_js_statement_in_code(content).match?(/^["']use client["'](?:;|\s|$)/)
|
|
129
152
|
end
|
|
130
153
|
|
|
154
|
+
# Patterns that indicate a file likely uses client-side features.
|
|
155
|
+
# Used as a heuristic warning — false positives (e.g., patterns in comments) are acceptable
|
|
156
|
+
# because this is a warning, not an error.
|
|
157
|
+
CLIENT_API_PATTERN = /\b(useState|useEffect|useReducer|useCallback|useMemo|useRef|useLayoutEffect|useImperativeHandle|useContext|useSyncExternalStore|useTransition|useDeferredValue)\b|\b(onClick|onChange|onSubmit|onFocus|onBlur|onKeyDown|onKeyUp|onKeyPress|onMouseDown|onMouseUp|onMouseEnter|onMouseLeave)\s*[={]|\bextends\s+(React\.)?(Component|PureComponent)\b/ # rubocop:disable Layout/LineLength
|
|
158
|
+
|
|
159
|
+
def warn_if_likely_client_component(file_path, component)
|
|
160
|
+
content = File.read(file_path)
|
|
161
|
+
matches = content.scan(CLIENT_API_PATTERN).flatten.compact.reject(&:empty?).uniq
|
|
162
|
+
|
|
163
|
+
return if matches.empty?
|
|
164
|
+
|
|
165
|
+
puts Rainbow(
|
|
166
|
+
"[react_on_rails] WARNING: '#{component}' (#{file_path}) appears to use client-side APIs " \
|
|
167
|
+
"(#{matches.first(3).join(', ')}#{matches.length > 3 ? ', ...' : ''}) " \
|
|
168
|
+
"but is missing the 'use client' directive. It will be registered as a server component.\n" \
|
|
169
|
+
"If this is a client component, add '\"use client\";' as the first line of the file."
|
|
170
|
+
).yellow
|
|
171
|
+
end
|
|
172
|
+
|
|
131
173
|
def pack_file_contents(file_path)
|
|
132
174
|
registered_component_name = component_name(file_path)
|
|
133
175
|
load_server_components = ReactOnRails::Utils.rsc_support_enabled?
|
|
134
176
|
|
|
135
177
|
if load_server_components && !client_entrypoint?(file_path)
|
|
178
|
+
warn_if_likely_client_component(file_path, registered_component_name)
|
|
179
|
+
|
|
136
180
|
return <<~FILE_CONTENT.strip
|
|
137
181
|
import registerServerComponent from '#{react_on_rails_npm_package}/registerServerComponent/client';
|
|
138
182
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: react_on_rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 16.4.0.rc.
|
|
4
|
+
version: 16.4.0.rc.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Gordon
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-02-
|
|
11
|
+
date: 2026-02-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: addressable
|