react_on_rails 10.1.4 → 11.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/CHANGELOG.md +14 -2
  4. data/CONTRIBUTING.md +0 -7
  5. data/Gemfile +8 -13
  6. data/README.md +5 -4
  7. data/app/helpers/react_on_rails_helper.rb +1 -534
  8. data/docs/additional-reading/caching-and-performance.md +2 -29
  9. data/docs/additional-reading/server-rendering-tips.md +4 -4
  10. data/docs/basics/configuration.md +23 -9
  11. data/docs/basics/i18n.md +4 -0
  12. data/lib/generators/react_on_rails/base_generator.rb +2 -3
  13. data/lib/generators/react_on_rails/dev_tests_generator.rb +1 -1
  14. data/lib/generators/react_on_rails/install_generator.rb +1 -1
  15. data/lib/generators/react_on_rails/react_no_redux_generator.rb +1 -1
  16. data/lib/generators/react_on_rails/react_with_redux_generator.rb +1 -1
  17. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb +2 -1
  18. data/lib/generators/react_on_rails/templates/dev_tests/spec/simplecov_helper.rb +2 -2
  19. data/lib/react_on_rails.rb +3 -2
  20. data/lib/react_on_rails/configuration.rb +27 -8
  21. data/lib/react_on_rails/error.rb +4 -0
  22. data/lib/react_on_rails/prerender_error.rb +1 -1
  23. data/lib/react_on_rails/react_on_rails_helper.rb +546 -0
  24. data/lib/react_on_rails/server_rendering_pool.rb +21 -11
  25. data/lib/react_on_rails/server_rendering_pool/{exec.rb → ruby_embedded_java_script.rb} +35 -38
  26. data/lib/react_on_rails/test_helper.rb +1 -1
  27. data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +2 -2
  28. data/lib/react_on_rails/utils.rb +12 -73
  29. data/lib/react_on_rails/version.rb +1 -1
  30. data/lib/react_on_rails/version_checker.rb +4 -2
  31. data/lib/react_on_rails/webpacker_utils.rb +42 -0
  32. data/package.json +1 -1
  33. data/rakelib/example_type.rb +1 -1
  34. data/rakelib/examples.rake +1 -1
  35. data/rakelib/release.rake +0 -5
  36. data/rakelib/task_helpers.rb +1 -1
  37. data/react_on_rails.gemspec +12 -9
  38. metadata +30 -13
  39. data/lib/react_on_rails/server_rendering_pool/node.rb +0 -81
  40. data/lib/react_on_rails/test_helper/node_process_launcher.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cf2961b3168fa3053ed43d36eda3e644c37ec679
4
- data.tar.gz: 4e6aa1237e92667735efeb47092b58466e93b040
3
+ metadata.gz: 2ab30d4fd951ada88bca651522969b8e0e11f2cb
4
+ data.tar.gz: b3e8aee757c36eb7c33e50334e6d1314e96983ef
5
5
  SHA512:
6
- metadata.gz: 9d1ed494bfb5f01d3cfec7946459070145543104b483ee35612d683031e4027e89d8d47e64811a40a29d321d22985c4da5a7741375198a53dc0c3cd3be8308b9
7
- data.tar.gz: 9cd73592f960f72e22d0318daa3c87d8f22d07513d0121997467140f854b43da062e1f03da10fdadec8c6b503d997421ad6d0bae2e8a83819f7ffb75ad707995
6
+ metadata.gz: 194cf31c9a5e2ea90e82ced1edc653f8c795ac5f29d8af01b4a3eb53a174b07c1453b73d041f60d16bd36c758cf308c42c970525f2b6a600b7dfc6da3c7c8fe9
7
+ data.tar.gz: 1f7ae2505fb7e88bb1e2906302d57866e11c7432040046c3638e1f91f78c8c8e7438ef6b69376c8e3a87ff66bca53029e1c18658470f7ad6f05c510b688f34b6
data/.rubocop.yml CHANGED
@@ -72,7 +72,7 @@ Metrics/PerceivedComplexity:
72
72
  Max: 10
73
73
 
74
74
  Metrics/ClassLength:
75
- Max: 140
75
+ Max: 150
76
76
 
77
77
  Metrics/ParameterLists:
78
78
  Max: 5
data/CHANGELOG.md CHANGED
@@ -6,6 +6,20 @@ Contributors: please follow the recommendations outlined at [keepachangelog.com]
6
6
  ## [Unreleased]
7
7
  Changes since last non-beta release.
8
8
 
9
+ *Please add entries here for your pull requests that are not yet released.*
10
+
11
+ ## MIGRATION for v11
12
+ - Unused `server_render_method` was removed from the configuration. If you want to use a custom renderer, contact justin@shakacode.com. We have a custom node rendering solution in production for egghead.io.
13
+ - Removed ReactOnRails::Utils.server_bundle_file_name and ReactOnRails::Utils.bundle_file_name. These are part of the performance features of "React on Rails Pro".
14
+ - Removed ENV["TRACE_REACT_ON_RAILS"] usage and replacing it with config.trace.
15
+
16
+ #### Enhanchements: Better Error Messages, Support for React on Rails Pro
17
+ - Tracing (debugging) options are simplified with a single `config.trace` setting that defaults to true for development and false otherwise.
18
+ - Calls to setTimeout, setInterval, clearTimeout will now always log some message if config.trace is true. Your JavaScript code should not be calling setTimout when server rendering.
19
+ - Errors raised are of type ReactOnRailsError, so you can see they came from React on Rails for debugging.
20
+ - Removed ReactOnRails::Utils.server_bundle_file_name and ReactOnRails::Utils.bundle_file_name.
21
+ - No longer logging the `railsContext` when server logging.
22
+
9
23
  ### [10.1.4] - 2018-04-11
10
24
 
11
25
  #### Fixed
@@ -15,8 +29,6 @@ Changes since last non-beta release.
15
29
 
16
30
  - Updated the default `build_production_command` that caused production assets to be built with development settings. [PR 1053](https://github.com/shakacode/react_on_rails/pull/1053) by [Roman Kushnir](https://github.com/RKushnir).
17
31
 
18
- *Please add entries here for your pull requests that are not yet released.*
19
-
20
32
  ### [10.1.3] - 2018-02-28
21
33
  #### Fixed
22
34
  - Improved error reporting on version mismatches between Javascript and Ruby packages. [PR 1025](https://github.com/shakacode/react_on_rails/pull/1025) by [theJoeBiz](https://github.com/squadette).
data/CONTRIBUTING.md CHANGED
@@ -223,13 +223,6 @@ Run `rake -T` or `rake -D` to see testing options.
223
223
 
224
224
  See below for verifying changes to the generators.
225
225
 
226
- ### Debugging
227
- Start the sample app like this for some debug printing:
228
-
229
- ```sh
230
- TRACE_REACT_ON_RAILS=true && foreman start
231
- ```
232
-
233
226
  ### Install Generator
234
227
  In your Rails app add this gem with a path to your fork.
235
228
 
data/Gemfile CHANGED
@@ -9,39 +9,34 @@ gemspec
9
9
  # They must be defined here because of the way Travis CI works, in that it will only
10
10
  # bundle install from a single Gemfile. Therefore, all gems that we will need for any dummy/example
11
11
  # app have to be manually added to this file.
12
+ gem "bootsnap", ">= 1.1.0", require: false
12
13
  gem "bootstrap-sass"
13
- gem "jbuilder", "~> 2.0"
14
+ gem "jbuilder"
14
15
  gem "jquery-rails"
15
16
  gem "mini_racer"
16
17
  gem "puma"
17
- gem "rails", "5.1.4"
18
+
18
19
  gem "rails_12factor"
19
- gem "rubocop", "~> 0.50", require: false
20
20
  gem "ruby-lint", require: false
21
- gem "sass-rails", "~> 5.0"
21
+ gem "sass-rails"
22
22
  gem "scss_lint", require: false
23
23
  gem "sdoc", group: :doc
24
24
  gem "spring"
25
25
  gem "sqlite3"
26
- gem "turbolinks", "~> 5.0"
26
+ gem "turbolinks"
27
27
  gem "uglifier"
28
28
  gem "web-console", group: :development
29
29
 
30
30
  # below are copied from spec/dummy/Gemfile
31
31
  gem "capybara"
32
32
  gem "capybara-screenshot"
33
- gem "rspec-rails"
34
- gem "rspec-retry"
35
- # Trouble installing on Sierra
36
- # gem "capybara-webkit"
37
33
  gem "chromedriver-helper"
38
34
  gem "launchy"
39
35
  gem "poltergeist"
36
+ gem "rspec-rails"
37
+ gem "rspec-retry"
40
38
  gem "selenium-webdriver"
41
- gem "webpacker", "3.0.2"
42
-
43
- # TODO: remove once we get out of beta.
44
- # gem 'webpacker', path: "../../forks/webpacker"
39
+ gem "webpacker"
45
40
 
46
41
  gem "equivalent-xml", github: "mbklein/equivalent-xml"
47
42
  gem "rainbow"
data/README.md CHANGED
@@ -34,9 +34,10 @@ First be sure to run `rails -v` and check that you are using Rails 5.1.3 or abov
34
34
  1. New Rails app: `rails new my-app --webpack=react`. `cd` into the directory.
35
35
  2. Add gem version: `gem 'react_on_rails', '10.0.2' # prefer exact gem version to match npm version`
36
36
  3. `bundle install`
37
- 4. Run the generator: `rails generate react_on_rails:install`
38
- 5. Start the app: `rails s`
39
- 6. Visit http://localhost:3000/hello_world
37
+ 4. Commit this to git (or else you cannot run the generator unless you pass the option --ignore-warnings).
38
+ 5. Run the generator: `rails generate react_on_rails:install`
39
+ 6. Start the app: `rails s`
40
+ 7. Visit http://localhost:3000/hello_world
40
41
 
41
42
  ### Turn on server rendering
42
43
 
@@ -272,7 +273,7 @@ See the [Installation Overview](docs/basics/installation-overview.md) for a conc
272
273
 
273
274
  ### Initializer Configuration
274
275
 
275
- Configure the `config/initializers/react_on_rails.rb`. You can adjust some necessary settings and defaults. See file [spec/dummy/config/initializers/react_on_rails.rb](https://github.com/shakacode/react_on_rails/tree/master/spec/dummy/config/initializers/react_on_rails.rb) for a detailed example of configuration, including comments on the different values to configure.
276
+ Configure the file `config/initializers/react_on_rails.rb`. You can adjust some necessary settings and defaults. See file [docs/basics/configuration.md](https://github.com/shakacode/react_on_rails/tree/master/docs/basics/configuration.md) for documentation of all configuration options.
276
277
 
277
278
  ### Including your React Component in your Rails Views
278
279
 
@@ -1,538 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # rubocop:disable Metrics/ModuleLength
4
- # NOTE:
5
- # For any heredoc JS:
6
- # 1. The white spacing in this file matters!
7
- # 2. Keep all #{some_var} fully to the left so that all indentation is done evenly in that var
8
- require "react_on_rails/prerender_error"
9
- require "addressable/uri"
10
- require "react_on_rails/utils"
11
- require "react_on_rails/json_output"
12
-
13
3
  module ReactOnRailsHelper
14
- include ReactOnRails::Utils::Required
15
-
16
- COMPONENT_HTML_KEY = "componentHtml".freeze
17
-
18
- # The env_javascript_include_tag and env_stylesheet_link_tag support the usage of a webpack
19
- # dev server for providing the JS and CSS assets during development mode. See
20
- # https://github.com/shakacode/react-webpack-rails-tutorial/ for a working example.
21
- #
22
- # The key options are `static` and `hot` which specify what you want for static vs. hot. Both of
23
- # these params are optional, and support either a single value, or an array.
24
- #
25
- # static vs. hot is picked based on whether
26
- # ENV["REACT_ON_RAILS_ENV"] == "HOT"
27
- #
28
- # <%= env_stylesheet_link_tag(static: 'application_static',
29
- # hot: 'application_non_webpack',
30
- # media: 'all',
31
- # 'data-turbolinks-track' => "reload") %>
32
- #
33
- # <!-- These do not use turbolinks, so no data-turbolinks-track -->
34
- # <!-- This is to load the hot assets. -->
35
- # <%= env_javascript_include_tag(hot: ['http://localhost:3500/vendor-bundle.js',
36
- # 'http://localhost:3500/app-bundle.js']) %>
37
- #
38
- # <!-- These do use turbolinks -->
39
- # <%= env_javascript_include_tag(static: 'application_static',
40
- # hot: 'application_non_webpack',
41
- # 'data-turbolinks-track' => "reload") %>
42
- #
43
- # NOTE: for Turbolinks 2.x, use 'data-turbolinks-track' => true
44
- # See application.html.erb for usage example
45
- # https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/app%2Fviews%2Flayouts%2Fapplication.html.erb
46
- def env_javascript_include_tag(args = {})
47
- send_tag_method(:javascript_include_tag, args)
48
- end
49
-
50
- # Helper to set CSS assets depending on if we want static or "hot", which means from the
51
- # Webpack dev server.
52
- #
53
- # In this example, application_non_webpack is simply a CSS asset pipeline file which includes
54
- # styles not placed in the webpack build.
55
- #
56
- # We don't need styles from the webpack build, as those will come via the JavaScript include
57
- # tags.
58
- #
59
- # The key options are `static` and `hot` which specify what you want for static vs. hot. Both of
60
- # these params are optional, and support either a single value, or an array.
61
- #
62
- # <%= env_stylesheet_link_tag(static: 'application_static',
63
- # hot: 'application_non_webpack',
64
- # media: 'all',
65
- # 'data-turbolinks-track' => true) %>
66
- #
67
- def env_stylesheet_link_tag(args = {})
68
- send_tag_method(:stylesheet_link_tag, args)
69
- end
70
-
71
- # react_component_name: can be a React component, created using a ES6 class, or
72
- # React.createClass, or a
73
- # `generator function` that returns a React component
74
- # using ES6
75
- # let MyReactComponentApp = (props, railsContext) => <MyReactComponent {...props}/>;
76
- # or using ES5
77
- # var MyReactComponentApp = function(props, railsContext) { return <YourReactComponent {...props}/>; }
78
- # Exposing the react_component_name is necessary to both a plain ReactComponent as well as
79
- # a generator:
80
- # See README.md for how to "register" your react components.
81
- # See spec/dummy/client/app/startup/serverRegistration.jsx and
82
- # spec/dummy/client/app/startup/ClientRegistration.jsx for examples of this
83
- #
84
- # options:
85
- # props: Ruby Hash or JSON string which contains the properties to pass to the react object. Do
86
- # not pass any props if you are separately initializing the store by the `redux_store` helper.
87
- # prerender: <true/false> set to false when debugging!
88
- # id: You can optionally set the id, or else a unique one is automatically generated.
89
- # html_options: You can set other html attributes that will go on this component
90
- # trace: <true/false> set to true to print additional debugging information in the browser
91
- # default is true for development, off otherwise
92
- # replay_console: <true/false> Default is true. False will disable echoing server rendering
93
- # logs to browser. While this can make troubleshooting server rendering difficult,
94
- # so long as you have the default configuration of logging_on_server set to
95
- # true, you'll still see the errors on the server.
96
- # raise_on_prerender_error: <true/false> Default to false. True will raise exception on server
97
- # if the JS code throws
98
- # Any other options are passed to the content tag, including the id.
99
- def react_component(component_name, raw_options = {})
100
- internal_result = internal_react_component(component_name, raw_options)
101
- server_rendered_html = internal_result["result"]["html"]
102
- console_script = internal_result["result"]["consoleReplayScript"]
103
-
104
- if server_rendered_html.is_a?(String)
105
- build_react_component_result_for_server_rendered_string(
106
- server_rendered_html: server_rendered_html,
107
- component_specification_tag: internal_result["tag"],
108
- console_script: console_script,
109
- options: internal_result["options"]
110
- )
111
- elsif server_rendered_html.is_a?(Hash)
112
- puts "[DEPRECATION] ReactOnRails: Use react_component_hash to return a Hash to your ruby view code"
113
- build_react_component_result_for_server_rendered_hash(
114
- server_rendered_html: server_rendered_html,
115
- component_specification_tag: internal_result["tag"],
116
- console_script: console_script,
117
- options: internal_result["options"]
118
- )
119
- else
120
- raise "server_rendered_html is expected to be a String. If you're trying to use a generator function to
121
- return a Hash to your ruby view code, then use react_component_hash instead of react_component and
122
- see https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx
123
- for an example of the necessary javascript configuration."
124
- end
125
- end
126
-
127
- def react_component_hash(component_name, raw_options = {})
128
- internal_result = internal_react_component(component_name, raw_options)
129
- server_rendered_html = internal_result["result"]["html"]
130
- console_script = internal_result["result"]["consoleReplayScript"]
131
-
132
- if server_rendered_html.is_a?(String) && internal_result["result"]["hasErrors"]
133
- server_rendered_html = { COMPONENT_HTML_KEY => internal_result["result"]["html"] }
134
- end
135
-
136
- if server_rendered_html.is_a?(Hash)
137
- build_react_component_result_for_server_rendered_hash(
138
- server_rendered_html: server_rendered_html,
139
- component_specification_tag: internal_result["tag"],
140
- console_script: console_script,
141
- options: internal_result["options"]
142
- )
143
- else
144
- raise "Generator function is expected to return an Object. See
145
- https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx
146
- for an example of the necessary javascript configuration."
147
- end
148
- end
149
-
150
- # Separate initialization of store from react_component allows multiple react_component calls to
151
- # use the same Redux store.
152
- #
153
- # store_name: name of the store, corresponding to your call to ReactOnRails.registerStores in your
154
- # JavaScript code.
155
- # props: Ruby Hash or JSON string which contains the properties to pass to the redux store.
156
- # Options
157
- # defer: false -- pass as true if you wish to render this below your component.
158
- def redux_store(store_name, props: {}, defer: false)
159
- redux_store_data = { store_name: store_name,
160
- props: props }
161
- if defer
162
- @registered_stores_defer_render ||= []
163
- @registered_stores_defer_render << redux_store_data
164
- "YOU SHOULD NOT SEE THIS ON YOUR VIEW -- Uses as a code block, like <% redux_store %> "\
165
- "and not <%= redux store %>"
166
- else
167
- @registered_stores ||= []
168
- @registered_stores << redux_store_data
169
- result = render_redux_store_data(redux_store_data)
170
- prepend_render_rails_context(result)
171
- end
172
- end
173
-
174
- # Place this view helper (no parameters) at the end of your shared layout. This tell
175
- # ReactOnRails where to client render the redux store hydration data. Since we're going
176
- # to be setting up the stores in the controllers, we need to know where on the view to put the
177
- # client side rendering of this hydration data, which is a hidden div with a matching class
178
- # that contains a data props.
179
- def redux_store_hydration_data
180
- return if @registered_stores_defer_render.blank?
181
- @registered_stores_defer_render.reduce("".dup) do |accum, redux_store_data|
182
- accum << render_redux_store_data(redux_store_data)
183
- end.html_safe
184
- end
185
-
186
- def sanitized_props_string(props)
187
- ReactOnRails::JsonOutput.escape(props.is_a?(String) ? props : props.to_json)
188
- end
189
-
190
- # Helper method to take javascript expression and returns the output from evaluating it.
191
- # If you have more than one line that needs to be executed, wrap it in an IIFE.
192
- # JS exceptions are caught and console messages are handled properly.
193
- def server_render_js(js_expression, options = {})
194
- wrapper_js = <<-JS.strip_heredoc
195
- (function() {
196
- var htmlResult = '';
197
- var consoleReplayScript = '';
198
- var hasErrors = false;
199
-
200
- try {
201
- htmlResult =
202
- (function() {
203
- return #{js_expression};
204
- })();
205
- } catch(e) {
206
- htmlResult = ReactOnRails.handleError({e: e, name: null,
207
- jsCode: '#{escape_javascript(js_expression)}', serverSide: true});
208
- hasErrors = true;
209
- }
210
-
211
- consoleReplayScript = ReactOnRails.buildConsoleReplay();
212
-
213
- return JSON.stringify({
214
- html: htmlResult,
215
- consoleReplayScript: consoleReplayScript,
216
- hasErrors: hasErrors
217
- });
218
-
219
- })()
220
- JS
221
-
222
- result = ReactOnRails::ServerRenderingPool.server_render_js_with_console_logging(wrapper_js)
223
-
224
- # IMPORTANT: To ensure that Rails doesn't auto-escape HTML tags, use the 'raw' method.
225
- html = result["html"]
226
- console_log_script = result["consoleLogScript"]
227
- raw("#{html}#{replay_console_option(options[:replay_console_option]) ? console_log_script : ''}")
228
- rescue ExecJS::ProgramError => err
229
- raise ReactOnRails::PrerenderError, component_name: "N/A (server_render_js called)",
230
- err: err,
231
- js_code: wrapper_js
232
- # rubocop:enable Style/RaiseArgs
233
- end
234
-
235
- def json_safe_and_pretty(hash_or_string)
236
- return "{}" if hash_or_string.nil?
237
- unless hash_or_string.class.in?([Hash, String])
238
- raise "#{__method__} only accepts String or Hash as argument "\
239
- "(#{hash_or_string.class} given)."
240
- end
241
-
242
- json_value = hash_or_string.is_a?(String) ? hash_or_string : hash_or_string.to_json
243
-
244
- ReactOnRails::JsonOutput.escape(json_value)
245
- end
246
-
247
- private
248
-
249
- def build_react_component_result_for_server_rendered_string(
250
- server_rendered_html: required("server_rendered_html"),
251
- component_specification_tag: required("component_specification_tag"),
252
- console_script: required("console_script"),
253
- options: required("options")
254
- )
255
- content_tag_options = options.html_options
256
- content_tag_options[:id] = options.dom_id
257
-
258
- rendered_output = content_tag(:div,
259
- server_rendered_html.html_safe,
260
- content_tag_options)
261
-
262
- result_console_script = options.replay_console ? console_script : ""
263
- result = compose_react_component_html_with_spec_and_console(
264
- component_specification_tag, rendered_output, result_console_script
265
- )
266
-
267
- prepend_render_rails_context(result)
268
- end
269
-
270
- def build_react_component_result_for_server_rendered_hash(
271
- server_rendered_html: required("server_rendered_html"),
272
- component_specification_tag: required("component_specification_tag"),
273
- console_script: required("console_script"),
274
- options: required("options")
275
- )
276
- content_tag_options = options.html_options
277
- content_tag_options[:id] = options.dom_id
278
-
279
- unless server_rendered_html[COMPONENT_HTML_KEY]
280
- raise "server_rendered_html hash expected to contain \"#{COMPONENT_HTML_KEY}\" key."
281
- end
282
-
283
- rendered_output = content_tag(:div,
284
- server_rendered_html[COMPONENT_HTML_KEY].html_safe,
285
- content_tag_options)
286
-
287
- result_console_script = options.replay_console ? console_script : ""
288
- result = compose_react_component_html_with_spec_and_console(
289
- component_specification_tag, rendered_output, result_console_script
290
- )
291
-
292
- # Other HTML strings need to be marked as html_safe too:
293
- server_rendered_hash_except_component = server_rendered_html.except(COMPONENT_HTML_KEY)
294
- server_rendered_hash_except_component.each do |key, html_string|
295
- server_rendered_hash_except_component[key] = html_string.html_safe
296
- end
297
-
298
- result_with_rails_context = prepend_render_rails_context(result)
299
- { COMPONENT_HTML_KEY => result_with_rails_context }.merge(
300
- server_rendered_hash_except_component
301
- )
302
- end
303
-
304
- def compose_react_component_html_with_spec_and_console(component_specification_tag, rendered_output, console_script)
305
- # IMPORTANT: Ensure that we mark string as html_safe to avoid escaping.
306
- # rubocop:disable Layout/IndentHeredoc
307
- <<-HTML.html_safe
308
- #{rendered_output}
309
- #{component_specification_tag}
310
- #{console_script}
311
- HTML
312
- # rubocop:enable Layout/IndentHeredoc
313
- end
314
-
315
- # prepend the rails_context if not yet applied
316
- def prepend_render_rails_context(render_value)
317
- return render_value if @rendered_rails_context
318
-
319
- data = rails_context(server_side: false)
320
-
321
- @rendered_rails_context = true
322
-
323
- rails_context_content = content_tag(:script,
324
- json_safe_and_pretty(data).html_safe,
325
- type: "application/json",
326
- id: "js-react-on-rails-context")
327
-
328
- "#{rails_context_content}\n#{render_value}".html_safe
329
- end
330
-
331
- def internal_react_component(component_name, raw_options = {})
332
- # Create the JavaScript and HTML to allow either client or server rendering of the
333
- # react_component.
334
- #
335
- # Create the JavaScript setup of the global to initialize the client rendering
336
- # (re-hydrate the data). This enables react rendered on the client to see that the
337
- # server has already rendered the HTML.
338
-
339
- options = ReactOnRails::ReactComponent::Options.new(name: component_name, options: raw_options)
340
-
341
- # Setup the page_loaded_js, which is the same regardless of prerendering or not!
342
- # The reason is that React is smart about not doing extra work if the server rendering did its job.
343
- component_specification_tag = content_tag(:script,
344
- json_safe_and_pretty(options.props).html_safe,
345
- type: "application/json",
346
- class: "js-react-on-rails-component",
347
- "data-component-name" => options.name,
348
- "data-trace" => (options.trace ? true : nil),
349
- "data-dom-id" => options.dom_id)
350
-
351
- # Create the HTML rendering part
352
- result = server_rendered_react_component_html(options.props,
353
- options.name,
354
- options.dom_id,
355
- prerender: options.prerender,
356
- trace: options.trace,
357
- raise_on_prerender_error: options.raise_on_prerender_error)
358
-
359
- { "options" => options, "tag" => component_specification_tag, "result" => result }
360
- end
361
-
362
- def render_redux_store_data(redux_store_data)
363
- result = content_tag(:script,
364
- json_safe_and_pretty(redux_store_data[:props]).html_safe,
365
- type: "application/json",
366
- "data-js-react-on-rails-store" => redux_store_data[:store_name].html_safe)
367
-
368
- prepend_render_rails_context(result)
369
- end
370
-
371
- def props_string(props)
372
- props.is_a?(String) ? props : props.to_json
373
- end
374
-
375
- # Returns Array [0]: html, [1]: script to console log
376
- # NOTE, these are NOT html_safe!
377
- def server_rendered_react_component_html(
378
- props, react_component_name, dom_id,
379
- prerender: required("prerender"),
380
- trace: required("trace"),
381
- raise_on_prerender_error: required("raise_on_prerender_error")
382
- )
383
- return { "html" => "", "consoleReplayScript" => "" } unless prerender
384
-
385
- # On server `location` option is added (`location = request.fullpath`)
386
- # React Router needs this to match the current route
387
-
388
- # Make sure that we use up-to-date bundle file used for server rendering, which is defined
389
- # by config file value for config.server_bundle_js_file
390
- ReactOnRails::ServerRenderingPool.reset_pool_if_server_bundle_was_modified
391
-
392
- # Since this code is not inserted on a web page, we don't need to escape props
393
- #
394
- # However, as JSON (returned from `props_string(props)`) isn't JavaScript,
395
- # but we want treat it as such, we need to compensate for the difference.
396
- #
397
- # \u2028 and \u2029 are valid characters in strings in JSON, but are treated
398
- # as newline separators in JavaScript. As no newlines are allowed in
399
- # strings in JavaScript, this causes an exception.
400
- #
401
- # We fix this by replacing these unicode characters with their escaped versions.
402
- # This should be safe, as the only place they can appear is in strings anyway.
403
- #
404
- # Read more here: http://timelessrepo.com/json-isnt-a-javascript-subset
405
-
406
- # rubocop:disable Layout/IndentHeredoc
407
- wrapper_js = <<-JS
408
- (function() {
409
- var railsContext = #{rails_context(server_side: true).to_json};
410
- #{initialize_redux_stores}
411
- var props = #{props_string(props).gsub("\u2028", '\u2028').gsub("\u2029", '\u2029')};
412
- return ReactOnRails.serverRenderReactComponent({
413
- name: '#{react_component_name}',
414
- domNodeId: '#{dom_id}',
415
- props: props,
416
- trace: #{trace},
417
- railsContext: railsContext
418
- });
419
- })()
420
- JS
421
- # rubocop:enable Layout/IndentHeredoc
422
-
423
- result = ReactOnRails::ServerRenderingPool.server_render_js_with_console_logging(wrapper_js)
424
-
425
- if result["hasErrors"] && raise_on_prerender_error
426
- # We caught this exception on our backtrace handler
427
- raise ReactOnRails::PrerenderError, component_name: react_component_name,
428
- # Sanitize as this might be browser logged
429
- props: sanitized_props_string(props),
430
- err: nil,
431
- js_code: wrapper_js,
432
- console_messages: result["consoleReplayScript"]
433
- # rubocop:enable Style/RaiseArgs
434
- end
435
- result
436
- rescue ExecJS::ProgramError => err
437
- # This error came from execJs
438
- raise ReactOnRails::PrerenderError, component_name: react_component_name,
439
- # Sanitize as this might be browser logged
440
- props: sanitized_props_string(props),
441
- err: err,
442
- js_code: wrapper_js
443
- # rubocop:enable Style/RaiseArgs
444
- end
445
-
446
- def initialize_redux_stores
447
- return "" unless @registered_stores.present? || @registered_stores_defer_render.present?
448
- declarations = "var reduxProps, store, storeGenerator;\n".dup
449
- all_stores = (@registered_stores || []) + (@registered_stores_defer_render || [])
450
-
451
- result = <<-JS.dup
452
- ReactOnRails.clearHydratedStores();
453
- JS
454
-
455
- result << all_stores.each_with_object(declarations) do |redux_store_data, memo|
456
- store_name = redux_store_data[:store_name]
457
- props = props_string(redux_store_data[:props])
458
- memo << <<-JS.strip_heredoc
459
- reduxProps = #{props};
460
- storeGenerator = ReactOnRails.getStoreGenerator('#{store_name}');
461
- store = storeGenerator(reduxProps, railsContext);
462
- ReactOnRails.setStore('#{store_name}', store);
463
- JS
464
- end
465
- result
466
- end
467
-
468
- # This is the definitive list of the default values used for the rails_context, which is the
469
- # second parameter passed to both component and store generator functions.
470
- # rubocop:disable Metrics/AbcSize
471
- def rails_context(server_side: required("server_side"))
472
- @rails_context ||= begin
473
- result = {
474
- inMailer: in_mailer?,
475
- # Locale settings
476
- i18nLocale: I18n.locale,
477
- i18nDefaultLocale: I18n.default_locale
478
- }
479
- if defined?(request) && request.present?
480
- # Check for encoding of the request's original_url and try to force-encoding the
481
- # URLs as UTF-8. This situation can occur in browsers that do not encode the
482
- # entire URL as UTF-8 already, mostly on the Windows platform (IE11 and lower).
483
- original_url_normalized = request.original_url
484
- if original_url_normalized.encoding.to_s == "ASCII-8BIT"
485
- original_url_normalized = original_url_normalized.force_encoding("ISO-8859-1").encode("UTF-8")
486
- end
487
-
488
- # Using Addressable instead of standard URI to better deal with
489
- # non-ASCII characters (see https://github.com/shakacode/react_on_rails/pull/405)
490
- uri = Addressable::URI.parse(original_url_normalized)
491
- # uri = Addressable::URI.parse("http://foo.com:3000/posts?id=30&limit=5#time=1305298413")
492
-
493
- result.merge!(
494
- # URL settings
495
- href: uri.to_s,
496
- location: "#{uri.path}#{uri.query.present? ? "?#{uri.query}" : ''}",
497
- scheme: uri.scheme, # http
498
- host: uri.host, # foo.com
499
- port: uri.port,
500
- pathname: uri.path, # /posts
501
- search: uri.query, # id=30&limit=5
502
- httpAcceptLanguage: request.env["HTTP_ACCEPT_LANGUAGE"]
503
- )
504
- end
505
- if ReactOnRails.configuration.rendering_extension
506
- custom_context = ReactOnRails.configuration.rendering_extension.custom_context(self)
507
- result.merge!(custom_context) if custom_context
508
- end
509
- result
510
- end
511
-
512
- @rails_context.merge(serverSide: server_side)
513
- end
514
- # rubocop:enable Metrics/AbcSize
515
-
516
- def replay_console_option(val)
517
- val.nil? ? ReactOnRails.configuration.replay_console : val
518
- end
519
-
520
- def use_hot_reloading?
521
- ENV["REACT_ON_RAILS_ENV"] == "HOT"
522
- end
523
-
524
- def send_tag_method(tag_method_name, args)
525
- asset_type = use_hot_reloading? ? :hot : :static
526
- assets = Array(args[asset_type])
527
- options = args.delete_if { |key, _value| %i[hot static].include?(key) }
528
- send(tag_method_name, *assets, options) unless assets.empty?
529
- end
530
-
531
- def in_mailer?
532
- return false unless defined?(controller)
533
- return false unless defined?(ActionMailer::Base)
534
-
535
- controller.is_a?(ActionMailer::Base)
536
- end
4
+ include ReactOnRails::Helper
537
5
  end
538
- # rubocop:enable Metrics/ModuleLength