react_on_rails 11.0.0 → 11.1.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.
Files changed (61) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +1 -1
  3. data/.travis.yml +2 -2
  4. data/CHANGELOG.md +80 -3
  5. data/CONTRIBUTING.md +7 -8
  6. data/{docs/LICENSE.md → LICENSE.md} +1 -1
  7. data/PROJECTS.md +7 -1
  8. data/README.md +175 -637
  9. data/Rakefile +4 -1
  10. data/SUMMARY.md +39 -26
  11. data/docs/additional-reading/convert-rails-5-api-only-app.md +19 -0
  12. data/docs/additional-reading/credits.md +10 -0
  13. data/docs/additional-reading/rails-assets-relative-paths.md +2 -2
  14. data/docs/additional-reading/rails-engine-integration.md +7 -0
  15. data/docs/additional-reading/server-rendering-tips.md +2 -4
  16. data/docs/additional-reading/webpack.md +2 -2
  17. data/docs/api/javascript-api.md +21 -1
  18. data/docs/api/redux-store-api.md +100 -0
  19. data/docs/api/view-helpers-api.md +130 -0
  20. data/docs/articles.md +20 -0
  21. data/docs/basics/client-vs-server-rendering.md +17 -0
  22. data/docs/basics/configuration.md +39 -5
  23. data/docs/basics/deployment.md +6 -0
  24. data/docs/basics/{generator.md → generator-details.md} +4 -7
  25. data/docs/basics/generator-functions-and-railscontext.md +157 -0
  26. data/docs/basics/how-react-on-rails-works.md +40 -0
  27. data/docs/basics/installation-into-an-existing-rails-app.md +64 -0
  28. data/docs/basics/react-server-rendering.md +27 -0
  29. data/docs/{additional-reading → basics}/recommended-project-structure.md +26 -4
  30. data/docs/{additional-reading → basics}/rspec-configuration.md +12 -6
  31. data/docs/basics/upgrading-react-on-rails.md +3 -3
  32. data/docs/basics/webpack-configuration.md +29 -0
  33. data/docs/misc/doctrine.md +1 -1
  34. data/docs/{additional-reading → misc-pending}/code-splitting.md +8 -2
  35. data/docs/{basics/installation-overview.md → misc-pending/manual-installation-overview.md} +3 -8
  36. data/docs/{additional-reading → misc-pending}/rails-assets.md +3 -1
  37. data/docs/testimonials.md +11 -0
  38. data/docs/tutorial.md +4 -4
  39. data/lib/generators/USAGE +1 -1
  40. data/lib/react_on_rails/configuration.rb +139 -97
  41. data/lib/react_on_rails/{react_on_rails_helper.rb → helper.rb} +104 -79
  42. data/lib/react_on_rails/json_parse_error.rb +26 -0
  43. data/lib/react_on_rails/prerender_error.rb +48 -6
  44. data/lib/react_on_rails/react_component/render_options.rb +94 -0
  45. data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +58 -50
  46. data/lib/react_on_rails/server_rendering_pool.rb +4 -16
  47. data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +10 -6
  48. data/lib/react_on_rails/test_helper.rb +8 -7
  49. data/lib/react_on_rails/utils.rb +38 -6
  50. data/lib/react_on_rails/version.rb +1 -1
  51. data/lib/react_on_rails/version_syntax_converter.rb +14 -12
  52. data/lib/react_on_rails/webpacker_utils.rb +11 -8
  53. data/lib/react_on_rails.rb +4 -2
  54. data/package.json +3 -2
  55. data/rakelib/release.rake +1 -1
  56. data/react_on_rails.gemspec +2 -2
  57. metadata +32 -21
  58. data/docs/additional-reading/caching-and-performance.md +0 -4
  59. data/docs/additional-reading/node-server-rendering.md +0 -5
  60. data/docs/api/ruby-api.md +0 -8
  61. data/lib/react_on_rails/react_component/options.rb +0 -64
@@ -1,103 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # NOTE: ReactOnRails::Utils.using_webpacker? always will return false when called here.
4
-
5
3
  module ReactOnRails
6
4
  def self.configure
7
5
  yield(configuration)
8
- setup_config_values
6
+ configuration.setup_config_values
9
7
  end
10
8
 
11
9
  DEFAULT_GENERATED_ASSETS_DIR = File.join(%w[public webpack], Rails.env).freeze
12
-
13
- def self.setup_config_values
14
- ensure_webpack_generated_files_exists
15
- configure_generated_assets_dirs_deprecation
16
- configure_skip_display_none_deprecation
17
- ensure_generated_assets_dir_present
18
- ensure_server_bundle_js_file_has_no_path
19
- check_i18n_directory_exists
20
- check_i18n_yml_directory_exists
21
- check_server_render_method_is_only_execjs
22
- end
23
-
24
- def self.check_server_render_method_is_only_execjs
25
- return if @configuration.server_render_method.blank? ||
26
- @configuration.server_render_method == "ExecJS"
27
-
28
- msg = <<-MSG.strip_heredoc
29
- Error configuring /config/react_on_rails.rb: invalid value for `config.server_render_method`.
30
- If you wish to use a server render method other than ExecJS, contact justin@shakacode.com
31
- for details.
32
- MSG
33
- raise ReactOnRails::Error, msg
34
- end
35
-
36
- def self.check_i18n_directory_exists
37
- return if @configuration.i18n_dir.nil?
38
- return if Dir.exist?(@configuration.i18n_dir)
39
-
40
- msg = <<-MSG.strip_heredoc
41
- Error configuring /config/react_on_rails.rb: invalid value for `config.i18n_dir`.
42
- Directory does not exist: #{@configuration.i18n_dir}. Set to value to nil or comment it
43
- out if not using the React on Rails i18n feature.
44
- MSG
45
- raise ReactOnRails::Error, msg
46
- end
47
-
48
- def self.check_i18n_yml_directory_exists
49
- return if @configuration.i18n_yml_dir.nil?
50
- return if Dir.exist?(@configuration.i18n_yml_dir)
51
-
52
- msg = <<-MSG.strip_heredoc
53
- Error configuring /config/react_on_rails.rb: invalid value for `config.i18n_yml_dir`.
54
- Directory does not exist: #{@configuration.i18n_yml_dir}. Set to value to nil or comment it
55
- out if not using this i18n with React on Rails, or if you want to use all translation files.
56
- MSG
57
- raise ReactOnRails::Error, msg
58
- end
59
-
60
- def self.ensure_generated_assets_dir_present
61
- return if @configuration.generated_assets_dir.present?
62
-
63
- @configuration.generated_assets_dir = DEFAULT_GENERATED_ASSETS_DIR
64
- puts "ReactOnRails: Set generated_assets_dir to default: #{DEFAULT_GENERATED_ASSETS_DIR}"
65
- end
66
-
67
- def self.configure_generated_assets_dirs_deprecation
68
- return if @configuration.generated_assets_dirs.blank?
69
-
70
- puts "[DEPRECATION] ReactOnRails: Use config.generated_assets_dir rather than "\
71
- "generated_assets_dirs"
72
- if @configuration.generated_assets_dir.blank?
73
- @configuration.generated_assets_dir = @configuration.generated_assets_dirs
74
- else
75
- puts "[DEPRECATION] ReactOnRails. You have both generated_assets_dirs and "\
76
- "generated_assets_dir defined. Define ONLY generated_assets_dir"
77
- end
78
- end
79
-
80
- def self.ensure_webpack_generated_files_exists
81
- return unless @configuration.webpack_generated_files.empty?
82
-
83
- files = ["hello-world-bundle.js"]
84
- files << @configuration.server_bundle_js_file if @configuration.server_bundle_js_file.present?
85
-
86
- @configuration.webpack_generated_files = files
87
- end
88
-
89
- def self.ensure_server_bundle_js_file_has_no_path
90
- return unless @configuration.server_bundle_js_file.include?(File::SEPARATOR)
91
-
92
- puts "[DEPRECATION] ReactOnRails: remove path from server_bundle_js_file in configuration. "\
93
- "All generated files must go in #{@configuration.generated_assets_dir}"
94
- @configuration.server_bundle_js_file = File.basename(@configuration.server_bundle_js_file)
95
- end
96
-
97
- def self.configure_skip_display_none_deprecation
98
- return if @configuration.skip_display_none.nil?
99
- puts "[DEPRECATION] ReactOnRails: remove skip_display_none from configuration."
100
- end
10
+ DEFAULT_SERVER_RENDER_TIMEOUT = 20
11
+ DEFAULT_POOL_SIZE = 1
12
+ DEFAULT_RANDOM_DOM_ID = TRUE # for backwards compatability
101
13
 
102
14
  def self.configuration
103
15
  @configuration ||= Configuration.new(
@@ -112,8 +24,8 @@ module ReactOnRails
112
24
  raise_on_prerender_error: false,
113
25
  trace: Rails.env.development?,
114
26
  development_mode: Rails.env.development?,
115
- server_renderer_pool_size: 1,
116
- server_renderer_timeout: 20,
27
+ server_renderer_pool_size: DEFAULT_POOL_SIZE,
28
+ server_renderer_timeout: DEFAULT_SERVER_RENDER_TIMEOUT,
117
29
  skip_display_none: nil,
118
30
  # skip_display_none is deprecated
119
31
  webpack_generated_files: %w[manifest.json],
@@ -121,7 +33,8 @@ module ReactOnRails
121
33
  server_render_method: nil,
122
34
  symlink_non_digested_assets_regex: nil,
123
35
  build_test_command: "",
124
- build_production_command: ""
36
+ build_production_command: "",
37
+ random_dom_id: DEFAULT_RANDOM_DOM_ID
125
38
  )
126
39
  end
127
40
 
@@ -134,7 +47,7 @@ module ReactOnRails
134
47
  :webpack_generated_files, :rendering_extension, :build_test_command,
135
48
  :build_production_command,
136
49
  :i18n_dir, :i18n_yml_dir,
137
- :server_render_method, :symlink_non_digested_assets_regex
50
+ :server_render_method, :symlink_non_digested_assets_regex, :random_dom_id
138
51
 
139
52
  def initialize(node_modules_location: nil, server_bundle_js_file: nil, prerender: nil,
140
53
  replay_console: nil,
@@ -145,7 +58,7 @@ module ReactOnRails
145
58
  generated_assets_dir: nil, webpack_generated_files: nil,
146
59
  rendering_extension: nil, build_test_command: nil,
147
60
  build_production_command: nil,
148
- i18n_dir: nil, i18n_yml_dir: nil,
61
+ i18n_dir: nil, i18n_yml_dir: nil, random_dom_id: nil,
149
62
  server_render_method: nil, symlink_non_digested_assets_regex: nil)
150
63
  self.node_modules_location = node_modules_location.present? ? node_modules_location : Rails.root
151
64
  self.server_bundle_js_file = server_bundle_js_file
@@ -156,6 +69,7 @@ module ReactOnRails
156
69
  self.i18n_dir = i18n_dir
157
70
  self.i18n_yml_dir = i18n_yml_dir
158
71
 
72
+ self.random_dom_id = random_dom_id
159
73
  self.prerender = prerender
160
74
  self.replay_console = replay_console
161
75
  self.logging_on_server = logging_on_server
@@ -178,5 +92,133 @@ module ReactOnRails
178
92
  self.server_render_method = server_render_method
179
93
  self.symlink_non_digested_assets_regex = symlink_non_digested_assets_regex
180
94
  end
95
+
96
+ # on ReactOnRails
97
+ def setup_config_values
98
+ ensure_webpack_generated_files_exists
99
+ configure_generated_assets_dirs_deprecation
100
+ configure_skip_display_none_deprecation
101
+ ensure_generated_assets_dir_present
102
+ ensure_server_bundle_js_file_has_no_path
103
+ check_i18n_directory_exists
104
+ check_i18n_yml_directory_exists
105
+ check_server_render_method_is_only_execjs
106
+ error_if_using_webpacker_and_generated_assets_dir_not_match_public_output_path
107
+ end
108
+
109
+ private
110
+
111
+ def error_if_using_webpacker_and_generated_assets_dir_not_match_public_output_path
112
+ return unless ReactOnRails::WebpackerUtils.using_webpacker?
113
+
114
+ return if generated_assets_dir.blank?
115
+
116
+ webpacker_public_output_path = ReactOnRails::WebpackerUtils.webpacker_public_output_path
117
+
118
+ if File.expand_path(generated_assets_dir) == webpacker_public_output_path.to_s
119
+ Rails.logger.warn("You specified /config/initializers/react_on_rails.rb generated_assets_dir "\
120
+ "with Webpacker. Remove this line from your configuration file.")
121
+ else
122
+ msg = <<-MSG.strip_heredoc
123
+ Error configuring /config/initializers/react_on_rails.rb: You are using webpacker
124
+ and your specified value for generated_assets_dir = #{generated_assets_dir}
125
+ that does not match the value for public_output_path specified in
126
+ webpacker.yml = #{webpacker_public_output_path}. You should remove the configuration
127
+ value for "generated_assets_dir" from your config/initializers/react_on_rails.rb file.
128
+ MSG
129
+ raise ReactOnRails::Error, msg
130
+ end
131
+ end
132
+
133
+ def check_server_render_method_is_only_execjs
134
+ return if server_render_method.blank? ||
135
+ server_render_method == "ExecJS"
136
+
137
+ msg = <<-MSG.strip_heredoc
138
+ Error configuring /config/initializers/react_on_rails.rb: invalid value for `config.server_render_method`.
139
+ If you wish to use a server render method other than ExecJS, contact justin@shakacode.com
140
+ for details.
141
+ MSG
142
+ raise ReactOnRails::Error, msg
143
+ end
144
+
145
+ def check_i18n_directory_exists
146
+ return if i18n_dir.nil?
147
+ return if Dir.exist?(i18n_dir)
148
+
149
+ msg = <<-MSG.strip_heredoc
150
+ Error configuring /config/initializers/react_on_rails.rb: invalid value for `config.i18n_dir`.
151
+ Directory does not exist: #{i18n_dir}. Set to value to nil or comment it
152
+ out if not using the React on Rails i18n feature.
153
+ MSG
154
+ raise ReactOnRails::Error, msg
155
+ end
156
+
157
+ def check_i18n_yml_directory_exists
158
+ return if i18n_yml_dir.nil?
159
+ return if Dir.exist?(i18n_yml_dir)
160
+
161
+ msg = <<-MSG.strip_heredoc
162
+ Error configuring /config/initializers/react_on_rails.rb: invalid value for `config.i18n_yml_dir`.
163
+ Directory does not exist: #{i18n_yml_dir}. Set to value to nil or comment it
164
+ out if not using this i18n with React on Rails, or if you want to use all translation files.
165
+ MSG
166
+ raise ReactOnRails::Error, msg
167
+ end
168
+
169
+ def ensure_generated_assets_dir_present
170
+ return if generated_assets_dir.present? || ReactOnRails::WebpackerUtils.using_webpacker?
171
+
172
+ self.generated_assets_dir = DEFAULT_GENERATED_ASSETS_DIR
173
+ Rails.logger.warn "ReactOnRails: Set generated_assets_dir to default: #{DEFAULT_GENERATED_ASSETS_DIR}"
174
+ end
175
+
176
+ def configure_generated_assets_dirs_deprecation
177
+ return if generated_assets_dirs.blank?
178
+
179
+ if ReactOnRails::WebpackerUtils.using_webpacker?
180
+ webpacker_public_output_path = ReactOnRails::WebpackerUtils.webpacker_public_output_path
181
+ Rails.logger.warn "Error configuring config/initializers/react_on_rails. Define neither the "\
182
+ "generated_assets_dirs no the generated_assets_dir when using Webpacker. This is defined by "\
183
+ "public_output_path specified in webpacker.yml = #{webpacker_public_output_path}."
184
+ return
185
+ end
186
+
187
+ Rails.logger.warn "[DEPRECATION] ReactOnRails: Use config.generated_assets_dir rather than "\
188
+ "generated_assets_dirs"
189
+ if generated_assets_dir.blank?
190
+ self.generated_assets_dir = generated_assets_dirs
191
+ else
192
+ Rails.logger.warn "[DEPRECATION] ReactOnRails. You have both generated_assets_dirs and "\
193
+ "generated_assets_dir defined. Define ONLY generated_assets_dir if NOT using Webpacker"\
194
+ " and define neither if using Webpacker"
195
+ end
196
+ end
197
+
198
+ def ensure_webpack_generated_files_exists
199
+ return unless webpack_generated_files.empty?
200
+
201
+ files = ["hello-world-bundle.js"]
202
+ files << server_bundle_js_file if server_bundle_js_file.present?
203
+
204
+ self.webpack_generated_files = files
205
+ end
206
+
207
+ def ensure_server_bundle_js_file_has_no_path
208
+ return unless server_bundle_js_file.include?(File::SEPARATOR)
209
+
210
+ assets_dir = ReactOnRails::Utils.generated_assets_full_path
211
+ self.server_bundle_js_file = File.basename(server_bundle_js_file)
212
+
213
+ Rails.logger.warn do
214
+ "[DEPRECATION] ReactOnRails: remove path from server_bundle_js_file in configuration. "\
215
+ "All generated files must go in #{assets_dir}. Using file basename #{server_bundle_js_file}"
216
+ end
217
+ end
218
+
219
+ def configure_skip_display_none_deprecation
220
+ return if skip_display_none.nil?
221
+ Rails.logger.warn "[DEPRECATION] ReactOnRails: remove skip_display_none from configuration."
222
+ end
181
223
  end
182
224
  end
@@ -98,17 +98,18 @@ module ReactOnRails
98
98
  # raise_on_prerender_error: <true/false> Default to false. True will raise exception on server
99
99
  # if the JS code throws
100
100
  # Any other options are passed to the content tag, including the id.
101
- def react_component(component_name, raw_options = {})
102
- internal_result = internal_react_component(component_name, raw_options)
103
- server_rendered_html = internal_result["result"]["html"]
104
- console_script = internal_result["result"]["consoleReplayScript"]
101
+ # random_dom_id can be set to override the global default.
102
+ def react_component(component_name, options = {})
103
+ internal_result = internal_react_component(component_name, options)
104
+ server_rendered_html = internal_result[:result]["html"]
105
+ console_script = internal_result[:result]["consoleReplayScript"]
105
106
 
106
107
  if server_rendered_html.is_a?(String)
107
108
  build_react_component_result_for_server_rendered_string(
108
109
  server_rendered_html: server_rendered_html,
109
- component_specification_tag: internal_result["tag"],
110
+ component_specification_tag: internal_result[:tag],
110
111
  console_script: console_script,
111
- options: internal_result["options"]
112
+ render_options: internal_result[:render_options]
112
113
  )
113
114
  elsif server_rendered_html.is_a?(Hash)
114
115
  msg = <<-MSG.strip_heredoc
@@ -116,43 +117,61 @@ module ReactOnRails
116
117
  https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx
117
118
  for an example of the necessary javascript configuration."
118
119
  MSG
119
- raise ReactOnRailsPro::Error, msg
120
+ raise ReactOnRails::Error, msg
120
121
 
121
122
  else
122
123
  msg = <<-MSG.strip_heredoc
123
- ReactOnRails: server_rendered_html is expected to be a String. If you're trying to
124
- use a generator function to return a Hash to your ruby view code, then use
124
+ ReactOnRails: server_rendered_html is expected to be a String for #{component_name}. If you're
125
+ trying to use a generator function to return a Hash to your ruby view code, then use
125
126
  react_component_hash instead of react_component and see
126
127
  https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx
127
- for an example of the necessary javascript configuration."
128
+ for an example of the JavaScript code."
128
129
  MSG
129
- raise ReactOnRailsPro::Error, msg
130
+ raise ReactOnRails::Error, msg
130
131
  end
131
132
  end
132
133
 
133
- def react_component_hash(component_name, raw_options = {})
134
- internal_result = internal_react_component(component_name, raw_options)
135
- server_rendered_html = internal_result["result"]["html"]
136
- console_script = internal_result["result"]["consoleReplayScript"]
137
-
138
- if server_rendered_html.is_a?(String) && internal_result["result"]["hasErrors"]
139
- server_rendered_html = { COMPONENT_HTML_KEY => internal_result["result"]["html"] }
134
+ # react_component_hash is used to return multiple HTML strings for server rendering, such as for
135
+ # adding meta-tags to a page.
136
+ # It is exactly like react_component except for the following:
137
+ # 1. prerender: true is automatically added, as this method doesn't make sense for client only
138
+ # rendering.
139
+ # 2. Your JavaScript for server rendering must return an Object for the key server_rendered_html.
140
+ # 3. Your view code must expect an object and not a string.
141
+ #
142
+ # Here is an example of the view code:
143
+ # <% react_helmet_app = react_component_hash("ReactHelmetApp", prerender: true,
144
+ # props: { helloWorldData: { name: "Mr. Server Side Rendering"}},
145
+ # id: "react-helmet-0", trace: true) %>
146
+ # <% content_for :title do %>
147
+ # <%= react_helmet_app['title'] %>
148
+ # <% end %>
149
+ # <%= react_helmet_app["componentHtml"] %>
150
+ #
151
+ def react_component_hash(component_name, options = {})
152
+ options[:prerender] = true
153
+ internal_result = internal_react_component(component_name, options)
154
+ server_rendered_html = internal_result[:result]["html"]
155
+ console_script = internal_result[:result]["consoleReplayScript"]
156
+
157
+ if server_rendered_html.is_a?(String) && internal_result[:result]["hasErrors"]
158
+ server_rendered_html = { COMPONENT_HTML_KEY => internal_result[:result]["html"] }
140
159
  end
141
160
 
142
161
  if server_rendered_html.is_a?(Hash)
143
162
  build_react_component_result_for_server_rendered_hash(
144
163
  server_rendered_html: server_rendered_html,
145
- component_specification_tag: internal_result["tag"],
164
+ component_specification_tag: internal_result[:tag],
146
165
  console_script: console_script,
147
- options: internal_result["options"]
166
+ render_options: internal_result[:render_options]
148
167
  )
149
168
  else
150
169
  msg = <<-MSG.strip_heredoc
151
- Generator function used by react_component_hash is expected to return an Object. See
152
- https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx
153
- for an example of the necessary javascript configuration.
170
+ Generator function used by react_component_hash for #{component_name} is expected to return
171
+ an Object. See https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx
172
+ for an example of the JavaScript code."
154
173
  MSG
155
- raise ReactOnRailsPro::Error, msg
174
+ raise ReactOnRails::Error, msg
156
175
  end
157
176
  end
158
177
 
@@ -199,8 +218,12 @@ module ReactOnRails
199
218
  # Helper method to take javascript expression and returns the output from evaluating it.
200
219
  # If you have more than one line that needs to be executed, wrap it in an IIFE.
201
220
  # JS exceptions are caught and console messages are handled properly.
221
+ # Options include:{ prerender:, trace:, raise_on_prerender_error: }
202
222
  def server_render_js(js_expression, options = {})
203
- wrapper_js = <<-JS.strip_heredoc
223
+ render_options = ReactOnRails::ReactComponent::RenderOptions
224
+ .new(react_component_name: "generic-js", options: options)
225
+
226
+ js_code = <<-JS.strip_heredoc
204
227
  (function() {
205
228
  var htmlResult = '';
206
229
  var consoleReplayScript = '';
@@ -228,20 +251,21 @@ module ReactOnRails
228
251
  })()
229
252
  JS
230
253
 
231
- result = ReactOnRails::ServerRenderingPool.server_render_js_with_console_logging(wrapper_js)
254
+ result = ReactOnRails::ServerRenderingPool
255
+ .server_render_js_with_console_logging(js_code, render_options)
232
256
 
233
257
  html = result["html"]
234
258
  console_log_script = result["consoleLogScript"]
235
- raw("#{html}#{replay_console_option(options[:replay_console_option]) ? console_log_script : ''}")
259
+ raw("#{html}#{render_options.replay_console ? console_log_script : ''}")
236
260
  rescue ExecJS::ProgramError => err
237
261
  raise ReactOnRails::PrerenderError, component_name: "N/A (server_render_js called)",
238
262
  err: err,
239
- js_code: wrapper_js
263
+ js_code: js_code
240
264
  end
241
265
 
242
266
  def json_safe_and_pretty(hash_or_string)
243
267
  return "{}" if hash_or_string.nil?
244
- unless hash_or_string.class.in?([Hash, String])
268
+ unless hash_or_string.is_a?(String) || hash_or_string.is_a?(Hash)
245
269
  raise ReactOnRails::Error, "#{__method__} only accepts String or Hash as argument "\
246
270
  "(#{hash_or_string.class} given)."
247
271
  end
@@ -257,16 +281,16 @@ module ReactOnRails
257
281
  server_rendered_html: required("server_rendered_html"),
258
282
  component_specification_tag: required("component_specification_tag"),
259
283
  console_script: required("console_script"),
260
- options: required("options")
284
+ render_options: required("render_options")
261
285
  )
262
- content_tag_options = options.html_options
263
- content_tag_options[:id] = options.dom_id
286
+ content_tag_options = render_options.html_options
287
+ content_tag_options[:id] = render_options.dom_id
264
288
 
265
289
  rendered_output = content_tag(:div,
266
290
  server_rendered_html.html_safe,
267
291
  content_tag_options)
268
292
 
269
- result_console_script = options.replay_console ? console_script : ""
293
+ result_console_script = render_options.replay_console ? console_script : ""
270
294
  result = compose_react_component_html_with_spec_and_console(
271
295
  component_specification_tag, rendered_output, result_console_script
272
296
  )
@@ -278,20 +302,20 @@ module ReactOnRails
278
302
  server_rendered_html: required("server_rendered_html"),
279
303
  component_specification_tag: required("component_specification_tag"),
280
304
  console_script: required("console_script"),
281
- options: required("options")
305
+ render_options: required("render_options")
282
306
  )
283
- content_tag_options = options.html_options
284
- content_tag_options[:id] = options.dom_id
307
+ content_tag_options = render_options.html_options
308
+ content_tag_options[:id] = render_options.dom_id
285
309
 
286
310
  unless server_rendered_html[COMPONENT_HTML_KEY]
287
- raise ReactOnRailsPro::Error, "server_rendered_html hash expected to contain \"#{COMPONENT_HTML_KEY}\" key."
311
+ raise ReactOnRails::Error, "server_rendered_html hash expected to contain \"#{COMPONENT_HTML_KEY}\" key."
288
312
  end
289
313
 
290
314
  rendered_output = content_tag(:div,
291
315
  server_rendered_html[COMPONENT_HTML_KEY].html_safe,
292
316
  content_tag_options)
293
317
 
294
- result_console_script = options.replay_console ? console_script : ""
318
+ result_console_script = render_options.replay_console ? console_script : ""
295
319
  result = compose_react_component_html_with_spec_and_console(
296
320
  component_specification_tag, rendered_output, result_console_script
297
321
  )
@@ -335,7 +359,7 @@ module ReactOnRails
335
359
  "#{rails_context_content}\n#{render_value}".html_safe
336
360
  end
337
361
 
338
- def internal_react_component(component_name, raw_options = {})
362
+ def internal_react_component(react_component_name, options = {})
339
363
  # Create the JavaScript and HTML to allow either client or server rendering of the
340
364
  # react_component.
341
365
  #
@@ -343,27 +367,27 @@ module ReactOnRails
343
367
  # (re-hydrate the data). This enables react rendered on the client to see that the
344
368
  # server has already rendered the HTML.
345
369
 
346
- options = ReactOnRails::ReactComponent::Options.new(name: component_name, options: raw_options)
370
+ render_options = ReactOnRails::ReactComponent::RenderOptions.new(react_component_name: react_component_name,
371
+ options: options)
347
372
 
348
373
  # Setup the page_loaded_js, which is the same regardless of prerendering or not!
349
374
  # The reason is that React is smart about not doing extra work if the server rendering did its job.
350
375
  component_specification_tag = content_tag(:script,
351
- json_safe_and_pretty(options.props).html_safe,
376
+ json_safe_and_pretty(render_options.props).html_safe,
352
377
  type: "application/json",
353
378
  class: "js-react-on-rails-component",
354
- "data-component-name" => options.name,
355
- "data-trace" => (options.trace ? true : nil),
356
- "data-dom-id" => options.dom_id)
379
+ "data-component-name" => render_options.react_component_name,
380
+ "data-trace" => (render_options.trace ? true : nil),
381
+ "data-dom-id" => render_options.dom_id)
357
382
 
358
383
  # Create the HTML rendering part
359
- result = server_rendered_react_component_html(options.props,
360
- options.name,
361
- options.dom_id,
362
- prerender: options.prerender,
363
- trace: options.trace,
364
- raise_on_prerender_error: options.raise_on_prerender_error)
365
-
366
- { "options" => options, "tag" => component_specification_tag, "result" => result }
384
+ result = server_rendered_react_component(render_options)
385
+
386
+ {
387
+ render_options: render_options,
388
+ tag: component_specification_tag,
389
+ result: result
390
+ }
367
391
  end
368
392
 
369
393
  def render_redux_store_data(redux_store_data)
@@ -379,15 +403,12 @@ module ReactOnRails
379
403
  props.is_a?(String) ? props : props.to_json
380
404
  end
381
405
 
382
- # Returns Array [0]: html, [1]: script to console log
383
- # NOTE, these are NOT html_safe!
384
- def server_rendered_react_component_html(
385
- props, react_component_name, dom_id,
386
- prerender: required("prerender"),
387
- trace: required("trace"),
388
- raise_on_prerender_error: required("raise_on_prerender_error")
389
- )
390
- return { "html" => "", "consoleReplayScript" => "" } unless prerender
406
+ # Returns object with values that are NOT html_safe!
407
+ def server_rendered_react_component(render_options)
408
+ return { "html" => "", "consoleReplayScript" => "" } unless render_options.prerender
409
+
410
+ react_component_name = render_options.react_component_name
411
+ props = render_options.props
391
412
 
392
413
  # On server `location` option is added (`location = request.fullpath`)
393
414
  # React Router needs this to match the current route
@@ -411,53 +432,55 @@ module ReactOnRails
411
432
  # Read more here: http://timelessrepo.com/json-isnt-a-javascript-subset
412
433
 
413
434
  # rubocop:disable Layout/IndentHeredoc
414
- wrapper_js = <<-JS
435
+ js_code = <<-JS
415
436
  (function() {
416
437
  var railsContext = #{rails_context(server_side: true).to_json};
417
438
  #{initialize_redux_stores}
418
439
  var props = #{props_string(props).gsub("\u2028", '\u2028').gsub("\u2029", '\u2029')};
419
440
  return ReactOnRails.serverRenderReactComponent({
420
441
  name: '#{react_component_name}',
421
- domNodeId: '#{dom_id}',
442
+ domNodeId: '#{render_options.dom_id}',
422
443
  props: props,
423
- trace: #{trace},
444
+ trace: #{render_options.trace},
424
445
  railsContext: railsContext
425
446
  });
426
447
  })()
427
448
  JS
428
449
  # rubocop:enable Layout/IndentHeredoc
429
450
 
430
- result = ReactOnRails::ServerRenderingPool.server_render_js_with_console_logging(wrapper_js)
451
+ begin
452
+ result = ReactOnRails::ServerRenderingPool.server_render_js_with_console_logging(js_code, render_options)
453
+ rescue StandardError => err
454
+ # This error came from the renderer
455
+ raise ReactOnRails::PrerenderError, component_name: react_component_name,
456
+ # Sanitize as this might be browser logged
457
+ props: sanitized_props_string(props),
458
+ err: err,
459
+ js_code: js_code
460
+ end
431
461
 
432
- if result["hasErrors"] && raise_on_prerender_error
462
+ if result["hasErrors"] && render_options.raise_on_prerender_error
433
463
  # We caught this exception on our backtrace handler
434
464
  raise ReactOnRails::PrerenderError, component_name: react_component_name,
435
465
  # Sanitize as this might be browser logged
436
466
  props: sanitized_props_string(props),
437
467
  err: nil,
438
- js_code: wrapper_js,
468
+ js_code: js_code,
439
469
  console_messages: result["consoleReplayScript"]
440
470
 
441
471
  end
442
472
  result
443
- rescue ExecJS::ProgramError => err
444
- # This error came from execJs
445
- raise ReactOnRails::PrerenderError, component_name: react_component_name,
446
- # Sanitize as this might be browser logged
447
- props: sanitized_props_string(props),
448
- err: err,
449
- js_code: wrapper_js
450
473
  end
451
474
 
452
475
  def initialize_redux_stores
453
- return "" unless @registered_stores.present? || @registered_stores_defer_render.present?
454
- declarations = "var reduxProps, store, storeGenerator;\n".dup
455
- all_stores = (@registered_stores || []) + (@registered_stores_defer_render || [])
456
-
457
476
  result = <<-JS.dup
458
477
  ReactOnRails.clearHydratedStores();
459
478
  JS
460
479
 
480
+ return result unless @registered_stores.present? || @registered_stores_defer_render.present?
481
+ declarations = "var reduxProps, store, storeGenerator;\n".dup
482
+ all_stores = (@registered_stores || []) + (@registered_stores_defer_render || [])
483
+
461
484
  result << all_stores.each_with_object(declarations) do |redux_store_data, memo|
462
485
  store_name = redux_store_data[:store_name]
463
486
  props = props_string(redux_store_data[:props])
@@ -481,7 +504,9 @@ module ReactOnRails
481
504
  inMailer: in_mailer?,
482
505
  # Locale settings
483
506
  i18nLocale: I18n.locale,
484
- i18nDefaultLocale: I18n.default_locale
507
+ i18nDefaultLocale: I18n.default_locale,
508
+ rorVersion: ReactOnRails::VERSION,
509
+ rorPro: ReactOnRails::Utils.react_on_rails_pro?
485
510
  }
486
511
  if defined?(request) && request.present?
487
512
  # Check for encoding of the request's original_url and try to force-encoding the
@@ -0,0 +1,26 @@
1
+ module ReactOnRails
2
+ class JsonParseError < ::ReactOnRails::Error
3
+ attr_reader :json
4
+
5
+ def initialize(parse_error, json)
6
+ @json = json
7
+ @original_error = parse_error
8
+ super(parse_error.message)
9
+ end
10
+
11
+ def to_honeybadger_context
12
+ to_error_context
13
+ end
14
+
15
+ def raven_context
16
+ to_error_context
17
+ end
18
+
19
+ def to_error_context
20
+ {
21
+ original_error: @original_error,
22
+ json: @json
23
+ }
24
+ end
25
+ end
26
+ end