react_on_rails 14.0.5 → 14.1.0.rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ca8ba0235b9256e1b24f5afc2c874f258cf239d78ad6490e86ce4d918b26ecf4
4
- data.tar.gz: 54796b7b8067b98180bf0e41eb20c4d6bb0a4ea54594af8365eb95c74158d1b7
3
+ metadata.gz: fa19667ad2e9a246bdfa0fbe76ccd2df1c44a571c0c5adb74e66c2f0775e384a
4
+ data.tar.gz: aa0703cfe098dff2d85950936e58c9cb2affc08ac776402a78ac9bb61f294ac9
5
5
  SHA512:
6
- metadata.gz: c081c9e1b626d9c986f212c2c351fd1774fddaa9be301ae05d6c890b41c30f21871acb56002001b2bf64584da3932457ffc9da522463f3d49f7b73dd62852032
7
- data.tar.gz: 005f0dc9f274c6ba9edc8784efb679e6ba5bc7610b708c7c072f4301f4fe25d3a4bf887ad2e9449284f2bbce346dfec54f0e7bd54192ab8d3532c7d5b3004267
6
+ metadata.gz: 6758816cdedf80c7071be8c686a495e70338c39b18d613999fc46fb31d372bf21448b9f99d24f3e5d0c1f56809e1813bdeb1be4ffc8ecdd6a6d7654f8a74c62d
7
+ data.tar.gz: 3cb8d9ea6c601b29bf6baa9ebde16fdfe88ee6ad7243e3709efdb42c6ca485ba7affd9f6b0dc2836f3544af0f9a6c0639b50419f2e1312dd7f4a4927b0bc31c1
data/CHANGELOG.md CHANGED
@@ -18,6 +18,30 @@ Please follow the recommendations outlined at [keepachangelog.com](http://keepac
18
18
  ### [Unreleased]
19
19
  Changes since the last non-beta release.
20
20
 
21
+ #### Fixed
22
+
23
+ - Incorrect type and confusing name for `ReactOnRails.registerStore`, use `registerStoreGenerators` instead. [PR 1651](https://github.com/shakacode/react_on_rails/pull/1651) by [alexeyr-ci](https://github.com/alexeyr-ci).
24
+ - Changed the ReactOnRails' version checker to use `ReactOnRails.configuration.node_modules_location` to determine the location of the package.json that the `react-on-rails` dependency is expected to be set by.
25
+ - Also, all errors that would be raised by the version checking have been converted to `Rails.Logger` warnings to avoid any breaking changes. [PR 1657](https://github.com/shakacode/react_on_rails/pull/1657) by [judahmeek](https://github.com/judahmeek).
26
+ - Enable use as a `git:` dependency. [PR 1664](https://github.com/shakacode/react_on_rails/pull/1664) by [alexeyr-ci](https://github.com/alexeyr-ci).
27
+
28
+ #### Added
29
+ - Added streaming server rendering support:
30
+ - [PR #1633](https://github.com/shakacode/react_on_rails/pull/1633) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
31
+ - New `stream_react_component` helper for adding streamed components to views
32
+ - New `streamServerRenderedReactComponent` function in the react-on-rails package that uses React 18's `renderToPipeableStream` API
33
+ - Enables progressive page loading and improved performance for server-rendered React components
34
+ - Added support for replaying console logs that occur during server rendering of streamed React components. This enables debugging of server-side rendering issues by capturing and displaying console output on the client and on the server output. [PR #1647](https://github.com/shakacode/react_on_rails/pull/1647) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
35
+ - Added support for handling errors happening during server rendering of streamed React components. It handles errors that happen during the initial render and errors that happen inside suspense boundaries. [PR #1648](https://github.com/shakacode/react_on_rails/pull/1648) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
36
+ - Added support for passing options to `YAML.safe_load` when loading locale files with `config.i18n_yml_safe_load_options`. [PR #1668](https://github.com/shakacode/react_on_rails/pull/1668) by [dzirtusss](https://github.com/dzirtusss).
37
+
38
+ #### Changed
39
+ - Console replay script generation now awaits the render request promise before generating, allowing it to capture console logs from asynchronous operations. This requires using a version of the Node renderer that supports replaying async console logs. [PR #1649](https://github.com/shakacode/react_on_rails/pull/1649) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
40
+
41
+ ### [14.0.5] - 2024-08-20
42
+ #### Fixed
43
+ - Should force load react-components which send over turbo-stream [PR #1620](https://github.com/shakacode/react_on_rails/pull/1620) by [theforestvn88](https://github.com/theforestvn88).
44
+
21
45
  ### [14.0.4] - 2024-07-02
22
46
 
23
47
  #### Improved
@@ -1148,7 +1172,8 @@ Best done with Object destructing:
1148
1172
  ##### Fixed
1149
1173
  - Fix several generator-related issues.
1150
1174
 
1151
- [Unreleased]: https://github.com/shakacode/react_on_rails/compare/14.0.4...master
1175
+ [Unreleased]: https://github.com/shakacode/react_on_rails/compare/14.0.5...master
1176
+ [14.0.5]: https://github.com/shakacode/react_on_rails/compare/14.0.4...14.0.5
1152
1177
  [14.0.4]: https://github.com/shakacode/react_on_rails/compare/14.0.3...14.0.4
1153
1178
  [14.0.3]: https://github.com/shakacode/react_on_rails/compare/14.0.2...14.0.3
1154
1179
  [14.0.2]: https://github.com/shakacode/react_on_rails/compare/14.0.1...14.0.2
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- react_on_rails (14.0.4)
4
+ react_on_rails (15.0.0.alpha.1)
5
5
  addressable
6
6
  connection_pool
7
7
  execjs (~> 2.5)
data/README.md CHANGED
@@ -17,6 +17,7 @@
17
17
  [![Linting](https://github.com/shakacode/react_on_rails/actions/workflows/lint-js-and-ruby.yml/badge.svg)](https://github.com/shakacode/react_on_rails/actions/workflows/lint-js-and-ruby.yml)
18
18
 
19
19
  # News
20
+ * [React on Rails Pro](https://www.shakacode.com/react-on-rails-pro/) supports the latest features of React 18, including [React Server Components](https://react.dev/reference/rsc/server-components) and [streaming](https://react.dev/reference/react-dom/server/renderToPipeableStream). Contact [Justin Gordon](mailto:justin@shakacode.com) for more information.
20
21
  * ShakaCode now maintains the official successor to `rails/webpacker`, [`shakapacker`](https://github.com/shakacode/shakapacker).
21
22
  * Project is updated to support Rails 7 and Shakapacker v6+!
22
23
 
data/SUMMARY.md CHANGED
@@ -17,6 +17,7 @@ Here is the new link:
17
17
  + [How React on Rails Works](docs/outdated/how-react-on-rails-works.md)
18
18
  + [Client vs. Server Rendering](./docs/guides/client-vs-server-rendering.md)
19
19
  + [React Server Rendering](./docs/guides/react-server-rendering.md)
20
+ + [🚀 Next-Gen Server Rendering: Streaming with React 18's Latest APIs](./docs/guides/streaming-server-rendering.md)
20
21
  + [Render-Functions and the RailsContext](docs/guides/render-functions-and-railscontext.md)
21
22
  + [Caching and Performance: React on Rails Pro](https://github.com/shakacode/react_on_rails/wiki).
22
23
  + [Deployment](docs/guides/deployment.md).
@@ -52,6 +52,7 @@ module ReactOnRails
52
52
  :generated_assets_dirs, :generated_assets_dir, :components_subdirectory,
53
53
  :webpack_generated_files, :rendering_extension, :build_test_command,
54
54
  :build_production_command, :i18n_dir, :i18n_yml_dir, :i18n_output_format,
55
+ :i18n_yml_safe_load_options,
55
56
  :server_render_method, :random_dom_id, :auto_load_bundle,
56
57
  :same_bundle_for_client_and_server, :rendering_props_extension,
57
58
  :make_generated_server_bundle_the_entrypoint,
@@ -69,7 +70,7 @@ module ReactOnRails
69
70
  rendering_extension: nil, build_test_command: nil,
70
71
  build_production_command: nil, defer_generated_component_packs: nil,
71
72
  same_bundle_for_client_and_server: nil,
72
- i18n_dir: nil, i18n_yml_dir: nil, i18n_output_format: nil,
73
+ i18n_dir: nil, i18n_yml_dir: nil, i18n_output_format: nil, i18n_yml_safe_load_options: nil,
73
74
  random_dom_id: nil, server_render_method: nil, rendering_props_extension: nil,
74
75
  components_subdirectory: nil, auto_load_bundle: nil, force_load: nil)
75
76
  self.node_modules_location = node_modules_location.present? ? node_modules_location : Rails.root
@@ -80,6 +81,7 @@ module ReactOnRails
80
81
  self.i18n_dir = i18n_dir
81
82
  self.i18n_yml_dir = i18n_yml_dir
82
83
  self.i18n_output_format = i18n_output_format
84
+ self.i18n_yml_safe_load_options = i18n_yml_safe_load_options
83
85
 
84
86
  self.random_dom_id = random_dom_id
85
87
  self.prerender = prerender
@@ -5,9 +5,7 @@ require "rails/railtie"
5
5
  module ReactOnRails
6
6
  class Engine < ::Rails::Engine
7
7
  config.to_prepare do
8
- if File.exist?(VersionChecker::NodePackageVersion.package_json_path)
9
- VersionChecker.build.raise_if_gem_and_node_package_versions_differ
10
- end
8
+ VersionChecker.build.log_if_gem_and_node_package_versions_differ
11
9
  ReactOnRails::ServerRenderingPool.reset_pool
12
10
  end
13
11
  end
@@ -91,6 +91,64 @@ module ReactOnRails
91
91
  end
92
92
  end
93
93
 
94
+ # Streams a server-side rendered React component using React's `renderToPipeableStream`.
95
+ # Supports React 18 features like Suspense, concurrent rendering, and selective hydration.
96
+ # Enables progressive rendering and improved performance for large components.
97
+ #
98
+ # Note: This function can only be used with React on Rails Pro.
99
+ # The view that uses this function must be rendered using the
100
+ # `stream_view_containing_react_components` method from the React on Rails Pro gem.
101
+ #
102
+ # Example of an async React component that can benefit from streaming:
103
+ #
104
+ # const AsyncComponent = async () => {
105
+ # const data = await fetchData();
106
+ # return <div>{data}</div>;
107
+ # };
108
+ #
109
+ # function App() {
110
+ # return (
111
+ # <Suspense fallback={<div>Loading...</div>}>
112
+ # <AsyncComponent />
113
+ # </Suspense>
114
+ # );
115
+ # }
116
+ #
117
+ # @param [String] component_name Name of your registered component
118
+ # @param [Hash] options Options for rendering
119
+ # @option options [Hash] :props Props to pass to the react component
120
+ # @option options [String] :dom_id DOM ID of the component container
121
+ # @option options [Hash] :html_options Options passed to content_tag
122
+ # @option options [Boolean] :prerender Set to false to disable server-side rendering
123
+ # @option options [Boolean] :trace Set to true to add extra debugging information to the HTML
124
+ # @option options [Boolean] :raise_on_prerender_error Set to true to raise exceptions during server-side rendering
125
+ # Any other options are passed to the content tag, including the id.
126
+ def stream_react_component(component_name, options = {})
127
+ unless ReactOnRails::Utils.react_on_rails_pro?
128
+ raise ReactOnRails::Error,
129
+ "You must use React on Rails Pro to use the stream_react_component method."
130
+ end
131
+
132
+ if @rorp_rendering_fibers.nil?
133
+ raise ReactOnRails::Error,
134
+ "You must call stream_view_containing_react_components to render the view containing the react component"
135
+ end
136
+
137
+ rendering_fiber = Fiber.new do
138
+ stream = internal_stream_react_component(component_name, options)
139
+ stream.each_chunk do |chunk|
140
+ Fiber.yield chunk
141
+ end
142
+ end
143
+
144
+ @rorp_rendering_fibers << rendering_fiber
145
+
146
+ # return the first chunk of the fiber
147
+ # It contains the initial html of the component
148
+ # all updates will be appended to the stream sent to browser
149
+ rendering_fiber.resume
150
+ end
151
+
94
152
  # react_component_hash is used to return multiple HTML strings for server rendering, such as for
95
153
  # adding meta-tags to a page.
96
154
  # It is exactly like react_component except for the following:
@@ -330,6 +388,16 @@ module ReactOnRails
330
388
 
331
389
  private
332
390
 
391
+ def internal_stream_react_component(component_name, options = {})
392
+ options = options.merge(stream?: true)
393
+ result = internal_react_component(component_name, options)
394
+ build_react_component_result_for_server_streamed_content(
395
+ rendered_html_stream: result[:result],
396
+ component_specification_tag: result[:tag],
397
+ render_options: result[:render_options]
398
+ )
399
+ end
400
+
333
401
  def generated_components_pack_path(component_name)
334
402
  "#{ReactOnRails::PackerUtils.packer_source_entry_path}/generated/#{component_name}.js"
335
403
  end
@@ -361,6 +429,32 @@ module ReactOnRails
361
429
  prepend_render_rails_context(result)
362
430
  end
363
431
 
432
+ def build_react_component_result_for_server_streamed_content(
433
+ rendered_html_stream:,
434
+ component_specification_tag:,
435
+ render_options:
436
+ )
437
+ is_first_chunk = true
438
+ rendered_html_stream.transform do |chunk_json_result|
439
+ if is_first_chunk
440
+ is_first_chunk = false
441
+ build_react_component_result_for_server_rendered_string(
442
+ server_rendered_html: chunk_json_result["html"],
443
+ component_specification_tag: component_specification_tag,
444
+ console_script: chunk_json_result["consoleReplayScript"],
445
+ render_options: render_options
446
+ )
447
+ else
448
+ result_console_script = render_options.replay_console ? chunk_json_result["consoleReplayScript"] : ""
449
+ # No need to prepend component_specification_tag or add rails context again
450
+ # as they're already included in the first chunk
451
+ compose_react_component_html_with_spec_and_console(
452
+ "", chunk_json_result["html"], result_console_script
453
+ )
454
+ end
455
+ end
456
+ end
457
+
364
458
  def build_react_component_result_for_server_rendered_hash(
365
459
  server_rendered_html: required("server_rendered_html"),
366
460
  component_specification_tag: required("component_specification_tag"),
@@ -397,27 +491,30 @@ module ReactOnRails
397
491
 
398
492
  def compose_react_component_html_with_spec_and_console(component_specification_tag, rendered_output, console_script)
399
493
  # IMPORTANT: Ensure that we mark string as html_safe to avoid escaping.
400
- <<~HTML.html_safe
494
+ html_content = <<~HTML
401
495
  #{rendered_output}
402
496
  #{component_specification_tag}
403
497
  #{console_script}
404
498
  HTML
499
+ html_content.strip.html_safe
405
500
  end
406
501
 
407
- # prepend the rails_context if not yet applied
408
- def prepend_render_rails_context(render_value)
409
- return render_value if @rendered_rails_context
502
+ def rails_context_if_not_already_rendered
503
+ return "" if @rendered_rails_context
410
504
 
411
505
  data = rails_context(server_side: false)
412
506
 
413
507
  @rendered_rails_context = true
414
508
 
415
- rails_context_content = content_tag(:script,
416
- json_safe_and_pretty(data).html_safe,
417
- type: "application/json",
418
- id: "js-react-on-rails-context")
509
+ content_tag(:script,
510
+ json_safe_and_pretty(data).html_safe,
511
+ type: "application/json",
512
+ id: "js-react-on-rails-context")
513
+ end
419
514
 
420
- "#{rails_context_content}\n#{render_value}".html_safe
515
+ # prepend the rails_context if not yet applied
516
+ def prepend_render_rails_context(render_value)
517
+ "#{rails_context_if_not_already_rendered}\n#{render_value}".strip.html_safe
421
518
  end
422
519
 
423
520
  def internal_react_component(react_component_name, options = {})
@@ -473,6 +570,25 @@ ReactOnRails.reactOnRailsComponentLoaded('#{render_options.dom_id}');
473
570
  props.is_a?(String) ? props : props.to_json
474
571
  end
475
572
 
573
+ def raise_prerender_error(json_result, react_component_name, props, js_code)
574
+ raise ReactOnRails::PrerenderError.new(
575
+ component_name: react_component_name,
576
+ props: sanitized_props_string(props),
577
+ err: nil,
578
+ js_code: js_code,
579
+ console_messages: json_result["consoleReplayScript"]
580
+ )
581
+ end
582
+
583
+ def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
584
+ chunk_json_result["hasErrors"] &&
585
+ (if chunk_json_result["isShellReady"]
586
+ render_options.raise_non_shell_server_rendering_errors
587
+ else
588
+ render_options.raise_on_prerender_error
589
+ end)
590
+ end
591
+
476
592
  # Returns object with values that are NOT html_safe!
477
593
  def server_rendered_react_component(render_options)
478
594
  return { "html" => "", "consoleReplayScript" => "" } unless render_options.prerender
@@ -520,16 +636,18 @@ ReactOnRails.reactOnRailsComponentLoaded('#{render_options.dom_id}');
520
636
  js_code: js_code)
521
637
  end
522
638
 
523
- if result["hasErrors"] && render_options.raise_on_prerender_error
524
- # We caught this exception on our backtrace handler
525
- raise ReactOnRails::PrerenderError.new(component_name: react_component_name,
526
- # Sanitize as this might be browser logged
527
- props: sanitized_props_string(props),
528
- err: nil,
529
- js_code: js_code,
530
- console_messages: result["consoleReplayScript"])
531
-
639
+ if render_options.stream?
640
+ result.transform do |chunk_json_result|
641
+ if should_raise_streaming_prerender_error?(chunk_json_result, render_options)
642
+ raise_prerender_error(chunk_json_result, react_component_name, props, js_code)
643
+ end
644
+ # It doesn't make any transformation, it listens and raises error if a chunk has errors
645
+ chunk_json_result
646
+ end
647
+ elsif result["hasErrors"] && render_options.raise_on_prerender_error
648
+ raise_prerender_error(result, react_component_name, props, js_code)
532
649
  end
650
+
533
651
  result
534
652
  end
535
653
 
@@ -115,11 +115,17 @@ module ReactOnRails
115
115
  translations = {}
116
116
  defaults = {}
117
117
  locale_files.each do |f|
118
- translation = YAML.safe_load(File.open(f))
118
+ safe_load_options = ReactOnRails.configuration.i18n_yml_safe_load_options || {}
119
+ translation = YAML.safe_load(File.open(f), **safe_load_options)
119
120
  key = translation.keys[0]
120
121
  val = flatten(translation[key])
121
122
  translations = translations.deep_merge(key => val)
122
123
  defaults = defaults.deep_merge(flatten_defaults(val)) if key == default_locale
124
+ rescue Psych::Exception => e
125
+ raise ReactOnRails::Error, <<~MSG
126
+ Error parsing #{f}: #{e.message}
127
+ Consider fixing unsafe YAML or permitting with config.i18n_yml_safe_load_options
128
+ MSG
123
129
  end
124
130
  [translations.to_json, defaults.to_json]
125
131
  end
@@ -87,6 +87,10 @@ module ReactOnRails
87
87
  retrieve_configuration_value_for(:raise_on_prerender_error)
88
88
  end
89
89
 
90
+ def raise_non_shell_server_rendering_errors
91
+ retrieve_react_on_rails_pro_config_value_for(:raise_non_shell_server_rendering_errors)
92
+ end
93
+
90
94
  def logging_on_server
91
95
  retrieve_configuration_value_for(:logging_on_server)
92
96
  end
@@ -107,6 +111,10 @@ module ReactOnRails
107
111
  options[key] = value
108
112
  end
109
113
 
114
+ def stream?
115
+ options[:stream?]
116
+ end
117
+
110
118
  private
111
119
 
112
120
  attr_reader :options
@@ -124,6 +132,14 @@ module ReactOnRails
124
132
  ReactOnRails.configuration.public_send(key)
125
133
  end
126
134
  end
135
+
136
+ def retrieve_react_on_rails_pro_config_value_for(key)
137
+ options.fetch(key) do
138
+ return nil unless ReactOnRails::Utils.react_on_rails_pro?
139
+
140
+ ReactOnRailsPro.configuration.public_send(key)
141
+ end
142
+ end
127
143
  end
128
144
  end
129
145
  end
@@ -46,7 +46,7 @@ module ReactOnRails
46
46
  # Note, js_code does not have to be based on React.
47
47
  # js_code MUST RETURN json stringify Object
48
48
  # Calling code will probably call 'html_safe' on return value before rendering to the view.
49
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity
49
+ # rubocop:disable Metrics/CyclomaticComplexity
50
50
  def exec_server_render_js(js_code, render_options, js_evaluator = nil)
51
51
  js_evaluator ||= self
52
52
  if render_options.trace
@@ -56,7 +56,11 @@ module ReactOnRails
56
56
  @file_index += 1
57
57
  end
58
58
  begin
59
- json_string = js_evaluator.eval_js(js_code, render_options)
59
+ result = if render_options.stream?
60
+ js_evaluator.eval_streaming_js(js_code, render_options)
61
+ else
62
+ js_evaluator.eval_js(js_code, render_options)
63
+ end
60
64
  rescue StandardError => err
61
65
  msg = <<~MSG
62
66
  Error evaluating server bundle. Check your webpack configuration.
@@ -71,26 +75,14 @@ module ReactOnRails
71
75
  end
72
76
  raise ReactOnRails::Error, msg, err.backtrace
73
77
  end
74
- result = nil
75
- begin
76
- result = JSON.parse(json_string)
77
- rescue JSON::ParserError => e
78
- raise ReactOnRails::JsonParseError.new(parse_error: e, json: json_string)
79
- end
80
78
 
81
- if render_options.logging_on_server
82
- console_script = result["consoleReplayScript"]
83
- console_script_lines = console_script.split("\n")
84
- console_script_lines = console_script_lines[2..-2]
85
- re = /console\.(?:log|error)\.apply\(console, \["\[SERVER\] (?<msg>.*)"\]\);/
86
- console_script_lines&.each do |line|
87
- match = re.match(line)
88
- Rails.logger.info { "[react_on_rails] #{match[:msg]}" } if match
89
- end
90
- end
91
- result
79
+ return parse_result_and_replay_console_messages(result, render_options) unless render_options.stream?
80
+
81
+ # Streamed component is returned as stream of strings.
82
+ # We need to parse each chunk and replay the console messages.
83
+ result.transform { |chunk| parse_result_and_replay_console_messages(chunk, render_options) }
92
84
  end
93
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity
85
+ # rubocop:enable Metrics/CyclomaticComplexity
94
86
 
95
87
  def trace_js_code_used(msg, js_code, file_name = "tmp/server-generated.js", force: false)
96
88
  return unless ReactOnRails.configuration.trace || force
@@ -233,6 +225,28 @@ module ReactOnRails
233
225
  msg = "file_url_to_string #{url} failed\nError is: #{e}"
234
226
  raise ReactOnRails::Error, msg
235
227
  end
228
+
229
+ def parse_result_and_replay_console_messages(result_string, render_options)
230
+ result = nil
231
+ begin
232
+ result = JSON.parse(result_string)
233
+ rescue JSON::ParserError => e
234
+ raise ReactOnRails::JsonParseError.new(parse_error: e, json: result_string)
235
+ end
236
+
237
+ if render_options.logging_on_server
238
+ console_script = result["consoleReplayScript"]
239
+ console_script_lines = console_script.split("\n")
240
+ # Regular expression to match console.log or console.error calls with SERVER prefix
241
+ re = /console\.(?:log|error)\.apply\(console, \["\[SERVER\] (?<msg>.*)"\]\);/
242
+ console_script_lines&.each do |line|
243
+ match = re.match(line)
244
+ # Log matched messages to Rails logger with react_on_rails prefix
245
+ Rails.logger.info { "[react_on_rails] #{match[:msg]}" } if match
246
+ end
247
+ end
248
+ result
249
+ end
236
250
  end
237
251
  # rubocop:enable Metrics/ClassLength
238
252
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ReactOnRails
4
- VERSION = "14.0.5"
4
+ VERSION = "14.1.0.rc.0"
5
5
  end
@@ -19,8 +19,9 @@ module ReactOnRails
19
19
  # For compatibility, the gem and the node package versions should always match,
20
20
  # unless the user really knows what they're doing. So we will give a
21
21
  # warning if they do not.
22
- def raise_if_gem_and_node_package_versions_differ
23
- return if node_package_version.relative_path?
22
+ def log_if_gem_and_node_package_versions_differ
23
+ return if node_package_version.raw.nil? || node_package_version.local_path_or_url?
24
+ return log_node_semver_version_warning if node_package_version.semver_wildcard?
24
25
 
25
26
  node_major_minor_patch = node_package_version.major_minor_patch
26
27
  gem_major_minor_patch = gem_major_minor_patch_version
@@ -28,9 +29,7 @@ module ReactOnRails
28
29
  node_major_minor_patch[1] == gem_major_minor_patch[1] &&
29
30
  node_major_minor_patch[2] == gem_major_minor_patch[2]
30
31
 
31
- raise_differing_versions_warning unless versions_match
32
-
33
- raise_node_semver_version_warning if node_package_version.semver_wildcard?
32
+ log_differing_versions_warning unless versions_match
34
33
  end
35
34
 
36
35
  private
@@ -46,15 +45,15 @@ module ReactOnRails
46
45
  MSG
47
46
  end
48
47
 
49
- def raise_differing_versions_warning
50
- msg = "**ERROR** ReactOnRails: ReactOnRails gem and node package versions do not match\n#{common_error_msg}"
51
- raise ReactOnRails::Error, msg
48
+ def log_differing_versions_warning
49
+ msg = "**WARNING** ReactOnRails: ReactOnRails gem and node package versions do not match\n#{common_error_msg}"
50
+ Rails.logger.warn(msg)
52
51
  end
53
52
 
54
- def raise_node_semver_version_warning
55
- msg = "**ERROR** ReactOnRails: Your node package version for react-on-rails contains a " \
53
+ def log_node_semver_version_warning
54
+ msg = "**WARNING** ReactOnRails: Your node package version for react-on-rails contains a " \
56
55
  "^ or ~\n#{common_error_msg}"
57
- raise ReactOnRails::Error, msg
56
+ Rails.logger.warn(msg)
58
57
  end
59
58
 
60
59
  def gem_version
@@ -74,7 +73,7 @@ module ReactOnRails
74
73
  end
75
74
 
76
75
  def self.package_json_path
77
- Rails.root.join("client", "package.json")
76
+ Rails.root.join(ReactOnRails.configuration.node_modules_location, "package.json")
78
77
  end
79
78
 
80
79
  def initialize(package_json)
@@ -82,29 +81,41 @@ module ReactOnRails
82
81
  end
83
82
 
84
83
  def raw
85
- parsed_package_contents = JSON.parse(package_json_contents)
86
- if parsed_package_contents.key?("dependencies") &&
87
- parsed_package_contents["dependencies"].key?("react-on-rails")
88
- parsed_package_contents["dependencies"]["react-on-rails"]
89
- else
90
- raise ReactOnRails::Error, "No 'react-on-rails' entry in package.json dependencies"
84
+ return @raw if defined?(@raw)
85
+
86
+ if File.exist?(package_json)
87
+ parsed_package_contents = JSON.parse(package_json_contents)
88
+ if parsed_package_contents.key?("dependencies") &&
89
+ parsed_package_contents["dependencies"].key?("react-on-rails")
90
+ return @raw = parsed_package_contents["dependencies"]["react-on-rails"]
91
+ end
91
92
  end
93
+ msg = "No 'react-on-rails' entry in the dependencies of #{NodePackageVersion.package_json_path}, " \
94
+ "which is the expected location according to ReactOnRails.configuration.node_modules_location"
95
+ Rails.logger.warn(msg)
96
+ @raw = nil
92
97
  end
93
98
 
94
99
  def semver_wildcard?
95
- raw.match(/[~^]/).present?
100
+ # See https://docs.npmjs.com/cli/v10/configuring-npm/package-json#dependencies
101
+ # We want to disallow all expressions other than exact versions
102
+ # and the ones allowed by local_path_or_url?
103
+ raw.blank? || raw.match(/[~^><|*-]/).present?
96
104
  end
97
105
 
98
- def relative_path?
99
- raw.match(%r{(\.\.|\Afile:///)}).present?
106
+ def local_path_or_url?
107
+ # See https://docs.npmjs.com/cli/v10/configuring-npm/package-json#dependencies
108
+ # All path and protocol "version ranges" include / somewhere,
109
+ # but we want to make an exception for npm:@scope/pkg@version.
110
+ !raw.nil? && raw.include?("/") && !raw.start_with?("npm:")
100
111
  end
101
112
 
102
113
  def major_minor_patch
103
- return if relative_path?
114
+ return if local_path_or_url?
104
115
 
105
116
  match = raw.match(MAJOR_MINOR_PATCH_VERSION_REGEX)
106
117
  unless match
107
- raise ReactOnRails::Error, "Cannot parse version number '#{raw}' (wildcard versions are not supported)"
118
+ raise ReactOnRails::Error, "Cannot parse version number '#{raw}' (only exact versions are supported)"
108
119
  end
109
120
 
110
121
  [match[1], match[2], match[3]]
data/tsconfig.json CHANGED
@@ -8,6 +8,7 @@
8
8
  "noImplicitAny": true,
9
9
  "outDir": "node_package/lib",
10
10
  "strict": true,
11
+ "incremental": true,
11
12
  "target": "es5"
12
13
  },
13
14
  "include": ["node_package/src/**/*"]
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: 14.0.5
4
+ version: 14.1.0.rc.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Gordon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-21 00:00:00.000000000 Z
11
+ date: 2025-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable