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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3604796380d91f73840216c85a1e00045afa0762d95922be147812f88c4f6a6d
4
- data.tar.gz: 6748c8e00a65a712276b2708f83d4c773b9c84be22365c5245627769b329b98c
3
+ metadata.gz: 122f40da16cc6a79e95e0679394667388c9cae337f19be2f61db4463a221a572
4
+ data.tar.gz: 38213ba983ecc6d2e6ea065de078a93305e6aaed6419069d5c94e427b3cef507
5
5
  SHA512:
6
- metadata.gz: 4b613ac18d9306d56efe97d27a017fdf19469ff6eaa4ede915d550de192853e071e40e65a443c5003e32211cb9858a62edd79abccc2620054cba9541d68fa94c
7
- data.tar.gz: 839a7a1aa64f36ce80c8c956825f969ee09732ed996498c1a2266c34d395464c9b5164b60f515297cf3fa6faea9197967df9a26952fc59605a25e269556cc9b4
6
+ metadata.gz: 82b8e04feec8ea4f73a8fc821adaa5343ce6fe4cf7e932c5b77711a2c0036b9b2d713834d4e900d530f310b25d2fcb746492e279615427c188e8b89e7f1a29c6
7
+ data.tar.gz: 0d06abde7b925466f835ec2646e9969348adbdc8cb8761a5995668ab0b607ec7a79621a6461553e4511a8cc654fb8b33d388b8e3bcc0ab331f93bf3c2d723e57
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- react_on_rails (16.4.0.rc.3)
4
+ react_on_rails (16.4.0.rc.5)
5
5
  addressable
6
6
  connection_pool
7
7
  execjs (~> 2.5)
@@ -61,12 +61,13 @@ module ReactOnRails
61
61
  .dev-services.yml.example
62
62
  bin/shakapacker-precompile-hook]
63
63
 
64
- # HelloWorld controller/layout only when not using RSC (RSC uses HelloServer)
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
- • Server-side rendering - Enabled with prerender option in app/views/#{route}/index.html.erb:
73
- <%= react_component("#{component_name}", props: @#{route}_props, prerender: true) %>
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
@@ -253,7 +253,8 @@ module ReactOnRails
253
253
 
254
254
  GeneratorMessages.add_info(GeneratorMessages.helpful_message_after_installation(
255
255
  component_name: component_name,
256
- route: route
256
+ route: route,
257
+ rsc: use_rsc?
257
258
  ))
258
259
  end
259
260
 
@@ -13,6 +13,8 @@
13
13
  # https://www.shakacode.com/react-on-rails-pro/docs/react-server-components/
14
14
 
15
15
  class HelloServerController < ApplicationController
16
+ layout "hello_world"
17
+
16
18
  include ReactOnRailsPro::Stream
17
19
 
18
20
  def index
@@ -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 = serverWebpackModule.extractLoader || ((rule, loaderName) => {
12
- if (!Array.isArray(rule.use)) return null;
13
- return rule.use.find((item) => {
14
- const testValue = typeof item === 'string' ? item : item.loader;
15
- return testValue && testValue.includes(loaderName);
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 loader before the JS loader (babel-loader or swc-loader)
30
- // This loader properly excludes client components from the RSC bundle
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 (Array.isArray(rule.use)) {
34
- // Find the JS loader (could be babel-loader or swc-loader depending on configuration)
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ReactOnRails
4
- VERSION = "16.4.0.rc.3"
4
+ VERSION = "16.4.0.rc.5"
5
5
  end
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.3
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-19 00:00:00.000000000 Z
11
+ date: 2026-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable