react_on_rails 11.0.0 → 12.0.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 +5 -5
- data/.circleci/config.yml +320 -0
- data/.eslintignore +2 -1
- data/.eslintrc +30 -2
- data/.github/FUNDING.yml +1 -0
- data/.gitignore +3 -1
- data/.prettierignore +10 -0
- data/.prettierrc +23 -0
- data/.release-it.json +3 -0
- data/.rubocop.yml +39 -11
- data/.travis.yml +11 -22
- data/CHANGELOG.md +235 -6
- data/CONTRIBUTING.md +64 -84
- data/Gemfile +3 -5
- data/KUDOS.md +4 -1
- data/{docs/LICENSE.md → LICENSE.md} +1 -1
- data/PROJECTS.md +7 -1
- data/REACT-ON-RAILS-PRO-LICENSE +95 -0
- data/README.md +233 -630
- data/Rakefile +1 -5
- data/SUMMARY.md +42 -29
- data/book.json +5 -5
- data/docs/additional-reading/asset-pipeline.md +8 -16
- data/docs/additional-reading/convert-rails-5-api-only-app.md +19 -0
- data/docs/additional-reading/credits.md +10 -0
- data/docs/additional-reading/images.md +1 -1
- data/docs/additional-reading/rails-engine-integration.md +7 -0
- data/docs/additional-reading/rails_view_rendering_from_inline_javascript.md +2 -1
- data/docs/additional-reading/react-helmet.md +30 -10
- data/docs/additional-reading/react-router.md +52 -75
- data/docs/additional-reading/server-rendering-tips.md +14 -11
- data/docs/additional-reading/upgrade-webpacker-v3-to-v4.md +10 -0
- data/docs/additional-reading/webpack.md +2 -2
- data/docs/api/javascript-api.md +24 -4
- data/docs/api/redux-store-api.md +102 -0
- data/docs/api/view-helpers-api.md +133 -0
- data/docs/articles.md +20 -0
- data/docs/basics/client-vs-server-rendering.md +23 -0
- data/docs/basics/configuration.md +145 -61
- data/docs/basics/deployment.md +4 -0
- data/docs/basics/{generator.md → generator-details.md} +5 -8
- data/docs/basics/heroku-deployment.md +24 -0
- data/docs/basics/hmr-and-hot-reloading-with-the-webpack-dev-server.md +49 -0
- data/docs/basics/i18n.md +45 -23
- data/docs/basics/installation-into-an-existing-rails-app.md +59 -0
- data/docs/basics/minitest-configuration.md +31 -0
- data/docs/basics/react-server-rendering.md +29 -0
- data/docs/{additional-reading → basics}/recommended-project-structure.md +38 -10
- data/docs/basics/render-functions-and-railscontext.md +205 -0
- data/docs/basics/rspec-configuration.md +73 -0
- data/docs/basics/upgrading-react-on-rails.md +75 -4
- data/docs/basics/webpack-configuration.md +50 -0
- data/docs/contributor-info/errors-with-hooks.md +45 -0
- data/docs/contributor-info/pull-requests.md +44 -0
- data/docs/misc/doctrine.md +2 -2
- data/docs/{additional-reading → outdated}/code-splitting.md +19 -9
- data/docs/outdated/how-react-on-rails-works.md +44 -0
- data/docs/{basics/installation-overview.md → outdated/manual-installation-overview.md} +8 -13
- data/docs/{additional-reading → outdated}/rails-assets-relative-paths.md +3 -3
- data/docs/{additional-reading → outdated}/rails-assets.md +5 -13
- data/docs/testimonials/hvmn.md +25 -0
- data/docs/testimonials/resortpass.md +13 -0
- data/docs/testimonials/testimonials.md +28 -0
- data/docs/tutorial.md +182 -44
- data/jest.config.js +4 -0
- data/lib/generators/USAGE +1 -1
- data/lib/generators/react_on_rails/base_generator.rb +2 -2
- data/lib/generators/react_on_rails/dev_tests_generator.rb +3 -2
- data/lib/generators/react_on_rails/generator_helper.rb +4 -6
- data/lib/generators/react_on_rails/install_generator.rb +2 -0
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev +3 -1
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-hmr +26 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +20 -40
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb +4 -1
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +4 -8
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.js +1 -3
- data/lib/react_on_rails/configuration.rb +175 -102
- data/lib/react_on_rails/error.rb +2 -0
- data/lib/react_on_rails/git_utils.rb +2 -0
- data/lib/react_on_rails/{react_on_rails_helper.rb → helper.rb} +201 -225
- data/lib/react_on_rails/json_output.rb +1 -1
- data/lib/react_on_rails/json_parse_error.rb +28 -0
- data/lib/react_on_rails/locales/base.rb +150 -0
- data/lib/react_on_rails/locales/to_js.rb +37 -0
- data/lib/react_on_rails/locales/to_json.rb +27 -0
- data/lib/react_on_rails/prerender_error.rb +56 -18
- data/lib/react_on_rails/react_component/render_options.rb +98 -0
- data/lib/react_on_rails/server_rendering_js_code.rb +42 -0
- data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +139 -106
- data/lib/react_on_rails/server_rendering_pool.rb +4 -16
- data/lib/react_on_rails/test_helper/ensure_assets_compiled.rb +7 -8
- data/lib/react_on_rails/test_helper/webpack_assets_compiler.rb +17 -0
- data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +10 -6
- data/lib/react_on_rails/test_helper.rb +18 -7
- data/lib/react_on_rails/utils.rb +66 -26
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_checker.rb +5 -1
- data/lib/react_on_rails/version_syntax_converter.rb +14 -12
- data/lib/react_on_rails/webpacker_utils.rb +44 -10
- data/lib/react_on_rails.rb +8 -3
- data/lib/tasks/assets.rake +33 -46
- data/lib/tasks/locale.rake +4 -2
- data/package-scripts.yml +49 -0
- data/package.json +49 -40
- data/rakelib/dummy_apps.rake +1 -9
- data/rakelib/example_type.rb +3 -1
- data/rakelib/examples.rake +3 -0
- data/rakelib/lint.rake +2 -7
- data/rakelib/node_package.rake +2 -2
- data/rakelib/release.rake +4 -8
- data/rakelib/run_rspec.rake +5 -18
- data/react_on_rails.gemspec +5 -6
- data/tsconfig.json +14 -0
- data/webpackConfigLoader.js +5 -4
- data/yarn.lock +7645 -1821
- metadata +71 -61
- data/Gemfile.rails32 +0 -74
- data/docs/additional-reading/babel.md +0 -5
- data/docs/additional-reading/caching-and-performance.md +0 -4
- data/docs/additional-reading/heroku-deployment.md +0 -92
- data/docs/additional-reading/hot-reloading-rails-development.md +0 -57
- data/docs/additional-reading/node-server-rendering.md +0 -5
- data/docs/additional-reading/rspec-configuration.md +0 -56
- data/docs/api/ruby-api-hot-reload-view-helpers.md +0 -44
- data/docs/api/ruby-api.md +0 -8
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-server +0 -12
- data/lib/react_on_rails/assets_precompile.rb +0 -150
- data/lib/react_on_rails/locales_to_js.rb +0 -134
- data/lib/react_on_rails/react_component/options.rb +0 -64
- /data/docs/{misc → outdated}/rails3.md +0 -0
|
@@ -15,68 +15,20 @@ module ReactOnRails
|
|
|
15
15
|
module Helper
|
|
16
16
|
include ReactOnRails::Utils::Required
|
|
17
17
|
|
|
18
|
-
COMPONENT_HTML_KEY = "componentHtml"
|
|
18
|
+
COMPONENT_HTML_KEY = "componentHtml"
|
|
19
19
|
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
20
|
+
# react_component_name: can be a React function or class component or a "render function".
|
|
21
|
+
# "render functions" differ from a React function in that they take two parameters, the
|
|
22
|
+
# props and the railsContext, like this:
|
|
23
23
|
#
|
|
24
|
-
#
|
|
25
|
-
# these params are optional, and support either a single value, or an array.
|
|
24
|
+
# let MyReactComponentApp = (props, railsContext) => <MyReactComponent {...props}/>;
|
|
26
25
|
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
26
|
+
# Alternately, you can define the render function with an additional property
|
|
27
|
+
# `.renderFunction = true`:
|
|
29
28
|
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
# media: 'all',
|
|
33
|
-
# 'data-turbolinks-track' => "reload") %>
|
|
29
|
+
# let MyReactComponentApp = (props) => <MyReactComponent {...props}/>;
|
|
30
|
+
# MyReactComponent.renderFunction = true;
|
|
34
31
|
#
|
|
35
|
-
# <!-- These do not use turbolinks, so no data-turbolinks-track -->
|
|
36
|
-
# <!-- This is to load the hot assets. -->
|
|
37
|
-
# <%= env_javascript_include_tag(hot: ['http://localhost:3500/vendor-bundle.js',
|
|
38
|
-
# 'http://localhost:3500/app-bundle.js']) %>
|
|
39
|
-
#
|
|
40
|
-
# <!-- These do use turbolinks -->
|
|
41
|
-
# <%= env_javascript_include_tag(static: 'application_static',
|
|
42
|
-
# hot: 'application_non_webpack',
|
|
43
|
-
# 'data-turbolinks-track' => "reload") %>
|
|
44
|
-
#
|
|
45
|
-
# NOTE: for Turbolinks 2.x, use 'data-turbolinks-track' => true
|
|
46
|
-
# See application.html.erb for usage example
|
|
47
|
-
# https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/app%2Fviews%2Flayouts%2Fapplication.html.erb
|
|
48
|
-
def env_javascript_include_tag(args = {})
|
|
49
|
-
send_tag_method(:javascript_include_tag, args)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Helper to set CSS assets depending on if we want static or "hot", which means from the
|
|
53
|
-
# Webpack dev server.
|
|
54
|
-
#
|
|
55
|
-
# In this example, application_non_webpack is simply a CSS asset pipeline file which includes
|
|
56
|
-
# styles not placed in the webpack build.
|
|
57
|
-
#
|
|
58
|
-
# We don't need styles from the webpack build, as those will come via the JavaScript include
|
|
59
|
-
# tags.
|
|
60
|
-
#
|
|
61
|
-
# The key options are `static` and `hot` which specify what you want for static vs. hot. Both of
|
|
62
|
-
# these params are optional, and support either a single value, or an array.
|
|
63
|
-
#
|
|
64
|
-
# <%= env_stylesheet_link_tag(static: 'application_static',
|
|
65
|
-
# hot: 'application_non_webpack',
|
|
66
|
-
# media: 'all',
|
|
67
|
-
# 'data-turbolinks-track' => true) %>
|
|
68
|
-
#
|
|
69
|
-
def env_stylesheet_link_tag(args = {})
|
|
70
|
-
send_tag_method(:stylesheet_link_tag, args)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# react_component_name: can be a React component, created using a ES6 class, or
|
|
74
|
-
# React.createClass, or a
|
|
75
|
-
# `generator function` that returns a React component
|
|
76
|
-
# using ES6
|
|
77
|
-
# let MyReactComponentApp = (props, railsContext) => <MyReactComponent {...props}/>;
|
|
78
|
-
# or using ES5
|
|
79
|
-
# var MyReactComponentApp = function(props, railsContext) { return <YourReactComponent {...props}/>; }
|
|
80
32
|
# Exposing the react_component_name is necessary to both a plain ReactComponent as well as
|
|
81
33
|
# a generator:
|
|
82
34
|
# See README.md for how to "register" your react components.
|
|
@@ -98,67 +50,96 @@ module ReactOnRails
|
|
|
98
50
|
# raise_on_prerender_error: <true/false> Default to false. True will raise exception on server
|
|
99
51
|
# if the JS code throws
|
|
100
52
|
# Any other options are passed to the content tag, including the id.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
53
|
+
# random_dom_id can be set to override the default from the config/initializers. That's only
|
|
54
|
+
# used if you have multiple instance of the same component on the Rails view.
|
|
55
|
+
def react_component(component_name, options = {})
|
|
56
|
+
internal_result = internal_react_component(component_name, options)
|
|
57
|
+
server_rendered_html = internal_result[:result]["html"]
|
|
58
|
+
console_script = internal_result[:result]["consoleReplayScript"]
|
|
105
59
|
|
|
106
60
|
if server_rendered_html.is_a?(String)
|
|
107
61
|
build_react_component_result_for_server_rendered_string(
|
|
108
62
|
server_rendered_html: server_rendered_html,
|
|
109
|
-
component_specification_tag: internal_result[
|
|
63
|
+
component_specification_tag: internal_result[:tag],
|
|
110
64
|
console_script: console_script,
|
|
111
|
-
|
|
65
|
+
render_options: internal_result[:render_options]
|
|
112
66
|
)
|
|
113
67
|
elsif server_rendered_html.is_a?(Hash)
|
|
114
|
-
msg =
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
68
|
+
msg = <<~MSG
|
|
69
|
+
Use react_component_hash (not react_component) to return a Hash to your ruby view code. See
|
|
70
|
+
https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx
|
|
71
|
+
for an example of the necessary javascript configuration.
|
|
118
72
|
MSG
|
|
119
|
-
raise
|
|
120
|
-
|
|
73
|
+
raise ReactOnRails::Error, msg
|
|
121
74
|
else
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
75
|
+
class_name = server_rendered_html.class.name
|
|
76
|
+
msg = <<~MSG
|
|
77
|
+
ReactOnRails: server_rendered_html is expected to be a String or Hash for #{component_name}.
|
|
78
|
+
Type is #{class_name}
|
|
79
|
+
Value:
|
|
80
|
+
#{server_rendered_html}
|
|
81
|
+
|
|
82
|
+
If you're trying to use a render function to return a Hash to your ruby view code, then use
|
|
83
|
+
react_component_hash instead of react_component and see
|
|
84
|
+
https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx
|
|
85
|
+
for an example of the JavaScript code.
|
|
128
86
|
MSG
|
|
129
|
-
raise
|
|
87
|
+
raise ReactOnRails::Error, msg
|
|
130
88
|
end
|
|
131
89
|
end
|
|
132
90
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
91
|
+
# react_component_hash is used to return multiple HTML strings for server rendering, such as for
|
|
92
|
+
# adding meta-tags to a page.
|
|
93
|
+
# It is exactly like react_component except for the following:
|
|
94
|
+
# 1. prerender: true is automatically added, as this method doesn't make sense for client only
|
|
95
|
+
# rendering.
|
|
96
|
+
# 2. Your JavaScript render function for server rendering must return an Object rather than a React component.
|
|
97
|
+
# 3. Your view code must expect an object and not a string.
|
|
98
|
+
#
|
|
99
|
+
# Here is an example of the view code:
|
|
100
|
+
# <% react_helmet_app = react_component_hash("ReactHelmetApp", prerender: true,
|
|
101
|
+
# props: { helloWorldData: { name: "Mr. Server Side Rendering"}},
|
|
102
|
+
# id: "react-helmet-0", trace: true) %>
|
|
103
|
+
# <% content_for :title do %>
|
|
104
|
+
# <%= react_helmet_app['title'] %>
|
|
105
|
+
# <% end %>
|
|
106
|
+
# <%= react_helmet_app["componentHtml"] %>
|
|
107
|
+
#
|
|
108
|
+
def react_component_hash(component_name, options = {})
|
|
109
|
+
options[:prerender] = true
|
|
110
|
+
internal_result = internal_react_component(component_name, options)
|
|
111
|
+
server_rendered_html = internal_result[:result]["html"]
|
|
112
|
+
console_script = internal_result[:result]["consoleReplayScript"]
|
|
113
|
+
|
|
114
|
+
if server_rendered_html.is_a?(String) && internal_result[:result]["hasErrors"]
|
|
115
|
+
server_rendered_html = { COMPONENT_HTML_KEY => internal_result[:result]["html"] }
|
|
140
116
|
end
|
|
141
117
|
|
|
142
118
|
if server_rendered_html.is_a?(Hash)
|
|
143
119
|
build_react_component_result_for_server_rendered_hash(
|
|
144
120
|
server_rendered_html: server_rendered_html,
|
|
145
|
-
component_specification_tag: internal_result[
|
|
121
|
+
component_specification_tag: internal_result[:tag],
|
|
146
122
|
console_script: console_script,
|
|
147
|
-
|
|
123
|
+
render_options: internal_result[:render_options]
|
|
148
124
|
)
|
|
149
125
|
else
|
|
150
|
-
msg =
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
126
|
+
msg = <<~MSG
|
|
127
|
+
render function used by react_component_hash for #{component_name} is expected to return
|
|
128
|
+
an Object. See https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx
|
|
129
|
+
for an example of the JavaScript code.
|
|
130
|
+
Note, your render function must either take 2 params or have the property
|
|
131
|
+
`.renderFunction = true` added to it to distinguish it from a React Function Component.
|
|
154
132
|
MSG
|
|
155
|
-
raise
|
|
133
|
+
raise ReactOnRails::Error, msg
|
|
156
134
|
end
|
|
157
135
|
end
|
|
158
136
|
|
|
159
137
|
# Separate initialization of store from react_component allows multiple react_component calls to
|
|
160
138
|
# use the same Redux store.
|
|
161
139
|
#
|
|
140
|
+
# NOTE: This technique not recommended as it prevents dynamic code splitting for performance.
|
|
141
|
+
# Instead, you should use the standard react_component view helper.
|
|
142
|
+
#
|
|
162
143
|
# store_name: name of the store, corresponding to your call to ReactOnRails.registerStores in your
|
|
163
144
|
# JavaScript code.
|
|
164
145
|
# props: Ruby Hash or JSON string which contains the properties to pass to the redux store.
|
|
@@ -187,6 +168,7 @@ module ReactOnRails
|
|
|
187
168
|
# that contains a data props.
|
|
188
169
|
def redux_store_hydration_data
|
|
189
170
|
return if @registered_stores_defer_render.blank?
|
|
171
|
+
|
|
190
172
|
@registered_stores_defer_render.reduce("".dup) do |accum, redux_store_data|
|
|
191
173
|
accum << render_redux_store_data(redux_store_data)
|
|
192
174
|
end.html_safe
|
|
@@ -199,8 +181,12 @@ module ReactOnRails
|
|
|
199
181
|
# Helper method to take javascript expression and returns the output from evaluating it.
|
|
200
182
|
# If you have more than one line that needs to be executed, wrap it in an IIFE.
|
|
201
183
|
# JS exceptions are caught and console messages are handled properly.
|
|
184
|
+
# Options include:{ prerender:, trace:, raise_on_prerender_error: }
|
|
202
185
|
def server_render_js(js_expression, options = {})
|
|
203
|
-
|
|
186
|
+
render_options = ReactOnRails::ReactComponent::RenderOptions
|
|
187
|
+
.new(react_component_name: "generic-js", options: options)
|
|
188
|
+
|
|
189
|
+
js_code = <<-JS.strip_heredoc
|
|
204
190
|
(function() {
|
|
205
191
|
var htmlResult = '';
|
|
206
192
|
var consoleReplayScript = '';
|
|
@@ -228,20 +214,22 @@ module ReactOnRails
|
|
|
228
214
|
})()
|
|
229
215
|
JS
|
|
230
216
|
|
|
231
|
-
result = ReactOnRails::ServerRenderingPool
|
|
217
|
+
result = ReactOnRails::ServerRenderingPool
|
|
218
|
+
.server_render_js_with_console_logging(js_code, render_options)
|
|
232
219
|
|
|
233
220
|
html = result["html"]
|
|
234
221
|
console_log_script = result["consoleLogScript"]
|
|
235
|
-
raw("#{html}#{
|
|
222
|
+
raw("#{html}#{render_options.replay_console ? console_log_script : ''}")
|
|
236
223
|
rescue ExecJS::ProgramError => err
|
|
237
224
|
raise ReactOnRails::PrerenderError, component_name: "N/A (server_render_js called)",
|
|
238
225
|
err: err,
|
|
239
|
-
js_code:
|
|
226
|
+
js_code: js_code
|
|
240
227
|
end
|
|
241
228
|
|
|
242
229
|
def json_safe_and_pretty(hash_or_string)
|
|
243
230
|
return "{}" if hash_or_string.nil?
|
|
244
|
-
|
|
231
|
+
|
|
232
|
+
unless hash_or_string.is_a?(String) || hash_or_string.is_a?(Hash)
|
|
245
233
|
raise ReactOnRails::Error, "#{__method__} only accepts String or Hash as argument "\
|
|
246
234
|
"(#{hash_or_string.class} given)."
|
|
247
235
|
end
|
|
@@ -251,22 +239,82 @@ module ReactOnRails
|
|
|
251
239
|
ReactOnRails::JsonOutput.escape(json_value)
|
|
252
240
|
end
|
|
253
241
|
|
|
242
|
+
# This is the definitive list of the default values used for the rails_context, which is the
|
|
243
|
+
# second parameter passed to both component and store render functions.
|
|
244
|
+
# This method can be called from views and from the controller, as `helpers.rails_context`
|
|
245
|
+
#
|
|
246
|
+
# rubocop:disable Metrics/AbcSize
|
|
247
|
+
def rails_context(server_side: true)
|
|
248
|
+
# ALERT: Keep in sync with node_package/src/types/index.ts for the properties of RailsContext
|
|
249
|
+
@rails_context ||= begin
|
|
250
|
+
result = {
|
|
251
|
+
railsEnv: Rails.env,
|
|
252
|
+
inMailer: in_mailer?,
|
|
253
|
+
# Locale settings
|
|
254
|
+
i18nLocale: I18n.locale,
|
|
255
|
+
i18nDefaultLocale: I18n.default_locale,
|
|
256
|
+
rorVersion: ReactOnRails::VERSION,
|
|
257
|
+
rorPro: ReactOnRails::Utils.react_on_rails_pro?
|
|
258
|
+
}
|
|
259
|
+
if defined?(request) && request.present?
|
|
260
|
+
# Check for encoding of the request's original_url and try to force-encoding the
|
|
261
|
+
# URLs as UTF-8. This situation can occur in browsers that do not encode the
|
|
262
|
+
# entire URL as UTF-8 already, mostly on the Windows platform (IE11 and lower).
|
|
263
|
+
original_url_normalized = request.original_url
|
|
264
|
+
if original_url_normalized.encoding.to_s == "ASCII-8BIT"
|
|
265
|
+
original_url_normalized = original_url_normalized.force_encoding("ISO-8859-1").encode("UTF-8")
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Using Addressable instead of standard URI to better deal with
|
|
269
|
+
# non-ASCII characters (see https://github.com/shakacode/react_on_rails/pull/405)
|
|
270
|
+
uri = Addressable::URI.parse(original_url_normalized)
|
|
271
|
+
# uri = Addressable::URI.parse("http://foo.com:3000/posts?id=30&limit=5#time=1305298413")
|
|
272
|
+
|
|
273
|
+
result.merge!(
|
|
274
|
+
# URL settings
|
|
275
|
+
href: uri.to_s,
|
|
276
|
+
location: "#{uri.path}#{uri.query.present? ? "?#{uri.query}" : ''}",
|
|
277
|
+
scheme: uri.scheme, # http
|
|
278
|
+
host: uri.host, # foo.com
|
|
279
|
+
port: uri.port,
|
|
280
|
+
pathname: uri.path, # /posts
|
|
281
|
+
search: uri.query, # id=30&limit=5
|
|
282
|
+
httpAcceptLanguage: request.env["HTTP_ACCEPT_LANGUAGE"]
|
|
283
|
+
)
|
|
284
|
+
end
|
|
285
|
+
if ReactOnRails.configuration.rendering_extension
|
|
286
|
+
custom_context = ReactOnRails.configuration.rendering_extension.custom_context(self)
|
|
287
|
+
result.merge!(custom_context) if custom_context
|
|
288
|
+
end
|
|
289
|
+
result
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
@rails_context.merge(serverSide: server_side)
|
|
293
|
+
end
|
|
294
|
+
# rubocop:enable Metrics/AbcSize
|
|
295
|
+
|
|
254
296
|
private
|
|
255
297
|
|
|
256
298
|
def build_react_component_result_for_server_rendered_string(
|
|
257
299
|
server_rendered_html: required("server_rendered_html"),
|
|
258
300
|
component_specification_tag: required("component_specification_tag"),
|
|
259
301
|
console_script: required("console_script"),
|
|
260
|
-
|
|
302
|
+
render_options: required("render_options")
|
|
261
303
|
)
|
|
262
|
-
content_tag_options =
|
|
263
|
-
content_tag_options
|
|
304
|
+
content_tag_options = render_options.html_options
|
|
305
|
+
if content_tag_options.key?(:tag)
|
|
306
|
+
content_tag_options_html_tag = content_tag_options[:tag]
|
|
307
|
+
content_tag_options.delete(:tag)
|
|
308
|
+
else
|
|
309
|
+
content_tag_options_html_tag = "div"
|
|
310
|
+
end
|
|
311
|
+
content_tag_options[:id] = render_options.dom_id
|
|
264
312
|
|
|
265
|
-
rendered_output = content_tag(
|
|
313
|
+
rendered_output = content_tag(content_tag_options_html_tag.to_sym,
|
|
266
314
|
server_rendered_html.html_safe,
|
|
267
315
|
content_tag_options)
|
|
268
316
|
|
|
269
|
-
result_console_script =
|
|
317
|
+
result_console_script = render_options.replay_console ? console_script : ""
|
|
270
318
|
result = compose_react_component_html_with_spec_and_console(
|
|
271
319
|
component_specification_tag, rendered_output, result_console_script
|
|
272
320
|
)
|
|
@@ -278,20 +326,20 @@ module ReactOnRails
|
|
|
278
326
|
server_rendered_html: required("server_rendered_html"),
|
|
279
327
|
component_specification_tag: required("component_specification_tag"),
|
|
280
328
|
console_script: required("console_script"),
|
|
281
|
-
|
|
329
|
+
render_options: required("render_options")
|
|
282
330
|
)
|
|
283
|
-
content_tag_options =
|
|
284
|
-
content_tag_options[:id] =
|
|
331
|
+
content_tag_options = render_options.html_options
|
|
332
|
+
content_tag_options[:id] = render_options.dom_id
|
|
285
333
|
|
|
286
334
|
unless server_rendered_html[COMPONENT_HTML_KEY]
|
|
287
|
-
raise
|
|
335
|
+
raise ReactOnRails::Error, "server_rendered_html hash expected to contain \"#{COMPONENT_HTML_KEY}\" key."
|
|
288
336
|
end
|
|
289
337
|
|
|
290
338
|
rendered_output = content_tag(:div,
|
|
291
339
|
server_rendered_html[COMPONENT_HTML_KEY].html_safe,
|
|
292
340
|
content_tag_options)
|
|
293
341
|
|
|
294
|
-
result_console_script =
|
|
342
|
+
result_console_script = render_options.replay_console ? console_script : ""
|
|
295
343
|
result = compose_react_component_html_with_spec_and_console(
|
|
296
344
|
component_specification_tag, rendered_output, result_console_script
|
|
297
345
|
)
|
|
@@ -310,13 +358,11 @@ module ReactOnRails
|
|
|
310
358
|
|
|
311
359
|
def compose_react_component_html_with_spec_and_console(component_specification_tag, rendered_output, console_script)
|
|
312
360
|
# IMPORTANT: Ensure that we mark string as html_safe to avoid escaping.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
#{
|
|
316
|
-
|
|
317
|
-
#{console_script}
|
|
361
|
+
<<~HTML.html_safe
|
|
362
|
+
#{rendered_output}
|
|
363
|
+
#{component_specification_tag}
|
|
364
|
+
#{console_script}
|
|
318
365
|
HTML
|
|
319
|
-
# rubocop:enable Layout/IndentHeredoc
|
|
320
366
|
end
|
|
321
367
|
|
|
322
368
|
# prepend the rails_context if not yet applied
|
|
@@ -335,7 +381,7 @@ module ReactOnRails
|
|
|
335
381
|
"#{rails_context_content}\n#{render_value}".html_safe
|
|
336
382
|
end
|
|
337
383
|
|
|
338
|
-
def internal_react_component(
|
|
384
|
+
def internal_react_component(react_component_name, options = {})
|
|
339
385
|
# Create the JavaScript and HTML to allow either client or server rendering of the
|
|
340
386
|
# react_component.
|
|
341
387
|
#
|
|
@@ -343,27 +389,27 @@ module ReactOnRails
|
|
|
343
389
|
# (re-hydrate the data). This enables react rendered on the client to see that the
|
|
344
390
|
# server has already rendered the HTML.
|
|
345
391
|
|
|
346
|
-
|
|
392
|
+
render_options = ReactOnRails::ReactComponent::RenderOptions.new(react_component_name: react_component_name,
|
|
393
|
+
options: options)
|
|
347
394
|
|
|
348
395
|
# Setup the page_loaded_js, which is the same regardless of prerendering or not!
|
|
349
396
|
# The reason is that React is smart about not doing extra work if the server rendering did its job.
|
|
350
397
|
component_specification_tag = content_tag(:script,
|
|
351
|
-
json_safe_and_pretty(
|
|
398
|
+
json_safe_and_pretty(render_options.props).html_safe,
|
|
352
399
|
type: "application/json",
|
|
353
400
|
class: "js-react-on-rails-component",
|
|
354
|
-
"data-component-name" =>
|
|
355
|
-
"data-trace" => (
|
|
356
|
-
"data-dom-id" =>
|
|
401
|
+
"data-component-name" => render_options.react_component_name,
|
|
402
|
+
"data-trace" => (render_options.trace ? true : nil),
|
|
403
|
+
"data-dom-id" => render_options.dom_id)
|
|
357
404
|
|
|
358
405
|
# Create the HTML rendering part
|
|
359
|
-
result =
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
{ "options" => options, "tag" => component_specification_tag, "result" => result }
|
|
406
|
+
result = server_rendered_react_component(render_options)
|
|
407
|
+
|
|
408
|
+
{
|
|
409
|
+
render_options: render_options,
|
|
410
|
+
tag: component_specification_tag,
|
|
411
|
+
result: result
|
|
412
|
+
}
|
|
367
413
|
end
|
|
368
414
|
|
|
369
415
|
def render_redux_store_data(redux_store_data)
|
|
@@ -379,15 +425,12 @@ module ReactOnRails
|
|
|
379
425
|
props.is_a?(String) ? props : props.to_json
|
|
380
426
|
end
|
|
381
427
|
|
|
382
|
-
# Returns
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
raise_on_prerender_error: required("raise_on_prerender_error")
|
|
389
|
-
)
|
|
390
|
-
return { "html" => "", "consoleReplayScript" => "" } unless prerender
|
|
428
|
+
# Returns object with values that are NOT html_safe!
|
|
429
|
+
def server_rendered_react_component(render_options)
|
|
430
|
+
return { "html" => "", "consoleReplayScript" => "" } unless render_options.prerender
|
|
431
|
+
|
|
432
|
+
react_component_name = render_options.react_component_name
|
|
433
|
+
props = render_options.props
|
|
391
434
|
|
|
392
435
|
# On server `location` option is added (`location = request.fullpath`)
|
|
393
436
|
# React Router needs this to match the current route
|
|
@@ -410,54 +453,48 @@ module ReactOnRails
|
|
|
410
453
|
#
|
|
411
454
|
# Read more here: http://timelessrepo.com/json-isnt-a-javascript-subset
|
|
412
455
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
(
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
name: '#{react_component_name}',
|
|
421
|
-
domNodeId: '#{dom_id}',
|
|
422
|
-
props: props,
|
|
423
|
-
trace: #{trace},
|
|
424
|
-
railsContext: railsContext
|
|
425
|
-
});
|
|
426
|
-
})()
|
|
427
|
-
JS
|
|
428
|
-
# rubocop:enable Layout/IndentHeredoc
|
|
456
|
+
js_code = ReactOnRails::ServerRenderingJsCode.server_rendering_component_js_code(
|
|
457
|
+
props_string: props_string(props).gsub("\u2028", '\u2028').gsub("\u2029", '\u2029'),
|
|
458
|
+
rails_context: rails_context(server_side: true).to_json,
|
|
459
|
+
redux_stores: initialize_redux_stores,
|
|
460
|
+
react_component_name: react_component_name,
|
|
461
|
+
render_options: render_options
|
|
462
|
+
)
|
|
429
463
|
|
|
430
|
-
|
|
464
|
+
begin
|
|
465
|
+
result = ReactOnRails::ServerRenderingPool.server_render_js_with_console_logging(js_code, render_options)
|
|
466
|
+
rescue StandardError => err
|
|
467
|
+
# This error came from the renderer
|
|
468
|
+
raise ReactOnRails::PrerenderError, component_name: react_component_name,
|
|
469
|
+
# Sanitize as this might be browser logged
|
|
470
|
+
props: sanitized_props_string(props),
|
|
471
|
+
err: err,
|
|
472
|
+
js_code: js_code
|
|
473
|
+
end
|
|
431
474
|
|
|
432
|
-
if result["hasErrors"] && raise_on_prerender_error
|
|
475
|
+
if result["hasErrors"] && render_options.raise_on_prerender_error
|
|
433
476
|
# We caught this exception on our backtrace handler
|
|
434
477
|
raise ReactOnRails::PrerenderError, component_name: react_component_name,
|
|
435
478
|
# Sanitize as this might be browser logged
|
|
436
479
|
props: sanitized_props_string(props),
|
|
437
480
|
err: nil,
|
|
438
|
-
js_code:
|
|
481
|
+
js_code: js_code,
|
|
439
482
|
console_messages: result["consoleReplayScript"]
|
|
440
483
|
|
|
441
484
|
end
|
|
442
485
|
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
486
|
end
|
|
451
487
|
|
|
452
488
|
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
489
|
result = <<-JS.dup
|
|
458
490
|
ReactOnRails.clearHydratedStores();
|
|
459
491
|
JS
|
|
460
492
|
|
|
493
|
+
return result unless @registered_stores.present? || @registered_stores_defer_render.present?
|
|
494
|
+
|
|
495
|
+
declarations = "var reduxProps, store, storeGenerator;\n".dup
|
|
496
|
+
all_stores = (@registered_stores || []) + (@registered_stores_defer_render || [])
|
|
497
|
+
|
|
461
498
|
result << all_stores.each_with_object(declarations) do |redux_store_data, memo|
|
|
462
499
|
store_name = redux_store_data[:store_name]
|
|
463
500
|
props = props_string(redux_store_data[:props])
|
|
@@ -471,71 +508,10 @@ module ReactOnRails
|
|
|
471
508
|
result
|
|
472
509
|
end
|
|
473
510
|
|
|
474
|
-
# This is the definitive list of the default values used for the rails_context, which is the
|
|
475
|
-
# second parameter passed to both component and store generator functions.
|
|
476
|
-
# rubocop:disable Metrics/AbcSize
|
|
477
|
-
def rails_context(server_side: required("server_side"))
|
|
478
|
-
@rails_context ||= begin
|
|
479
|
-
result = {
|
|
480
|
-
railsEnv: Rails.env,
|
|
481
|
-
inMailer: in_mailer?,
|
|
482
|
-
# Locale settings
|
|
483
|
-
i18nLocale: I18n.locale,
|
|
484
|
-
i18nDefaultLocale: I18n.default_locale
|
|
485
|
-
}
|
|
486
|
-
if defined?(request) && request.present?
|
|
487
|
-
# Check for encoding of the request's original_url and try to force-encoding the
|
|
488
|
-
# URLs as UTF-8. This situation can occur in browsers that do not encode the
|
|
489
|
-
# entire URL as UTF-8 already, mostly on the Windows platform (IE11 and lower).
|
|
490
|
-
original_url_normalized = request.original_url
|
|
491
|
-
if original_url_normalized.encoding.to_s == "ASCII-8BIT"
|
|
492
|
-
original_url_normalized = original_url_normalized.force_encoding("ISO-8859-1").encode("UTF-8")
|
|
493
|
-
end
|
|
494
|
-
|
|
495
|
-
# Using Addressable instead of standard URI to better deal with
|
|
496
|
-
# non-ASCII characters (see https://github.com/shakacode/react_on_rails/pull/405)
|
|
497
|
-
uri = Addressable::URI.parse(original_url_normalized)
|
|
498
|
-
# uri = Addressable::URI.parse("http://foo.com:3000/posts?id=30&limit=5#time=1305298413")
|
|
499
|
-
|
|
500
|
-
result.merge!(
|
|
501
|
-
# URL settings
|
|
502
|
-
href: uri.to_s,
|
|
503
|
-
location: "#{uri.path}#{uri.query.present? ? "?#{uri.query}" : ''}",
|
|
504
|
-
scheme: uri.scheme, # http
|
|
505
|
-
host: uri.host, # foo.com
|
|
506
|
-
port: uri.port,
|
|
507
|
-
pathname: uri.path, # /posts
|
|
508
|
-
search: uri.query, # id=30&limit=5
|
|
509
|
-
httpAcceptLanguage: request.env["HTTP_ACCEPT_LANGUAGE"]
|
|
510
|
-
)
|
|
511
|
-
end
|
|
512
|
-
if ReactOnRails.configuration.rendering_extension
|
|
513
|
-
custom_context = ReactOnRails.configuration.rendering_extension.custom_context(self)
|
|
514
|
-
result.merge!(custom_context) if custom_context
|
|
515
|
-
end
|
|
516
|
-
result
|
|
517
|
-
end
|
|
518
|
-
|
|
519
|
-
@rails_context.merge(serverSide: server_side)
|
|
520
|
-
end
|
|
521
|
-
|
|
522
|
-
# rubocop:enable Metrics/AbcSize
|
|
523
|
-
|
|
524
511
|
def replay_console_option(val)
|
|
525
512
|
val.nil? ? ReactOnRails.configuration.replay_console : val
|
|
526
513
|
end
|
|
527
514
|
|
|
528
|
-
def use_hot_reloading?
|
|
529
|
-
ENV["REACT_ON_RAILS_ENV"] == "HOT"
|
|
530
|
-
end
|
|
531
|
-
|
|
532
|
-
def send_tag_method(tag_method_name, args)
|
|
533
|
-
asset_type = use_hot_reloading? ? :hot : :static
|
|
534
|
-
assets = Array(args[asset_type])
|
|
535
|
-
options = args.delete_if { |key, _value| %i[hot static].include?(key) }
|
|
536
|
-
send(tag_method_name, *assets, options) unless assets.empty?
|
|
537
|
-
end
|
|
538
|
-
|
|
539
515
|
def in_mailer?
|
|
540
516
|
return false unless defined?(controller)
|
|
541
517
|
return false unless defined?(ActionMailer::Base)
|
|
@@ -11,7 +11,7 @@ module ReactOnRails
|
|
|
11
11
|
"\u2028" => '\u2028',
|
|
12
12
|
"\u2029" => '\u2029'
|
|
13
13
|
}.freeze
|
|
14
|
-
ESCAPE_REGEXP = /[\u2028\u2029&><]/u
|
|
14
|
+
ESCAPE_REGEXP = /[\u2028\u2029&><]/u.freeze
|
|
15
15
|
|
|
16
16
|
def self.escape(json)
|
|
17
17
|
return escape_without_erb_util(json) if Utils.rails_version_less_than_4_1_1
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReactOnRails
|
|
4
|
+
class JsonParseError < ::ReactOnRails::Error
|
|
5
|
+
attr_reader :json
|
|
6
|
+
|
|
7
|
+
def initialize(parse_error, json)
|
|
8
|
+
@json = json
|
|
9
|
+
@original_error = parse_error
|
|
10
|
+
super(parse_error.message)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_honeybadger_context
|
|
14
|
+
to_error_context
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def raven_context
|
|
18
|
+
to_error_context
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_error_context
|
|
22
|
+
{
|
|
23
|
+
original_error: @original_error,
|
|
24
|
+
json: @json
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|