react_on_rails 11.1.8 → 12.0.0.pre.beta.1
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 +4 -4
- 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 +37 -11
- data/.travis.yml +10 -20
- data/CHANGELOG.md +89 -3
- data/CONTRIBUTING.md +61 -80
- data/Gemfile +3 -5
- data/{COMM-LICENSE → REACT-ON-RAILS-PRO-LICENSE} +6 -9
- data/README.md +121 -71
- data/Rakefile +0 -7
- data/SUMMARY.md +10 -12
- data/book.json +5 -5
- data/docs/additional-reading/asset-pipeline.md +8 -16
- data/docs/additional-reading/images.md +1 -1
- 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 +12 -7
- data/docs/additional-reading/upgrade-webpacker-v3-to-v4.md +10 -0
- data/docs/api/javascript-api.md +3 -3
- data/docs/api/redux-store-api.md +4 -2
- data/docs/api/view-helpers-api.md +17 -14
- data/docs/basics/configuration.md +64 -61
- data/docs/basics/deployment.md +1 -2
- data/docs/basics/i18n.md +44 -22
- data/docs/basics/installation-into-an-existing-rails-app.md +2 -2
- data/docs/basics/minitest-configuration.md +31 -0
- data/docs/basics/react-server-rendering.md +1 -1
- data/docs/basics/recommended-project-structure.md +1 -1
- data/docs/basics/{generator-functions-and-railscontext.md → render-functions-and-railscontext.md} +59 -21
- data/docs/basics/rspec-configuration.md +2 -2
- data/docs/basics/upgrading-react-on-rails.md +61 -3
- data/docs/basics/webpack-configuration.md +15 -1
- data/docs/contributor-info/errors-with-hooks.md +45 -0
- data/docs/contributor-info/pull-requests.md +44 -0
- data/docs/misc/doctrine.md +1 -1
- data/docs/{misc-pending → outdated}/code-splitting.md +13 -9
- data/docs/{additional-reading → outdated}/heroku-deployment.md +0 -6
- data/docs/{basics → outdated}/how-react-on-rails-works.md +3 -3
- data/docs/{misc-pending → outdated}/manual-installation-overview.md +5 -5
- data/docs/{additional-reading → outdated}/rails-assets-relative-paths.md +3 -3
- data/docs/{misc-pending → outdated}/rails-assets.md +4 -7
- data/docs/{misc → outdated}/rails3.md +0 -0
- data/docs/testimonials/hvmn.md +3 -3
- data/docs/testimonials/resortpass.md +13 -0
- data/docs/testimonials/testimonials.md +11 -1
- data/docs/tutorial.md +69 -28
- data/jest.config.js +4 -0
- data/lib/generators/react_on_rails/base_generator.rb +2 -2
- data/lib/generators/react_on_rails/dev_tests_generator.rb +2 -1
- 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 +18 -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/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.rb +4 -1
- data/lib/react_on_rails/configuration.rb +15 -23
- data/lib/react_on_rails/error.rb +2 -0
- data/lib/react_on_rails/git_utils.rb +2 -0
- data/lib/react_on_rails/helper.rb +111 -162
- data/lib/react_on_rails/json_output.rb +1 -1
- data/lib/react_on_rails/json_parse_error.rb +2 -0
- data/lib/react_on_rails/locales/base.rb +142 -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 +11 -15
- data/lib/react_on_rails/react_component/render_options.rb +4 -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 +71 -51
- data/lib/react_on_rails/test_helper/ensure_assets_compiled.rb +7 -8
- data/lib/react_on_rails/utils.rb +15 -20
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_checker.rb +5 -1
- data/lib/react_on_rails/webpacker_utils.rb +16 -2
- data/lib/tasks/assets.rake +5 -45
- data/lib/tasks/locale.rake +8 -2
- data/package-scripts.yml +49 -0
- data/package.json +41 -31
- 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 +3 -8
- data/rakelib/run_rspec.rake +5 -18
- data/react_on_rails.gemspec +3 -5
- data/tsconfig.json +14 -0
- data/webpackConfigLoader.js +5 -4
- data/yarn.lock +7042 -2327
- metadata +39 -57
- data/Gemfile.rails32 +0 -74
- data/docs/additional-reading/babel.md +0 -5
- data/docs/additional-reading/hot-reloading-rails-development-asset-pipeline.md +0 -47
- data/docs/api/ruby-api-hot-reload-view-helpers.md +0 -44
- 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 -136
data/lib/react_on_rails/error.rb
CHANGED
|
@@ -6,8 +6,10 @@ module ReactOnRails
|
|
|
6
6
|
module GitUtils
|
|
7
7
|
def self.uncommitted_changes?(message_handler)
|
|
8
8
|
return false if ENV["COVERAGE"] == "true"
|
|
9
|
+
|
|
9
10
|
status = `git status --porcelain`
|
|
10
11
|
return false if $CHILD_STATUS.success? && status.empty?
|
|
12
|
+
|
|
11
13
|
error = if !$CHILD_STATUS.success?
|
|
12
14
|
"You do not have Git installed. Please install Git, and commit your changes before continuing"
|
|
13
15
|
else
|
|
@@ -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,7 +50,8 @@ 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
|
-
# random_dom_id can be set to override the
|
|
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.
|
|
102
55
|
def react_component(component_name, options = {})
|
|
103
56
|
internal_result = internal_react_component(component_name, options)
|
|
104
57
|
server_rendered_html = internal_result[:result]["html"]
|
|
@@ -112,20 +65,24 @@ module ReactOnRails
|
|
|
112
65
|
render_options: internal_result[:render_options]
|
|
113
66
|
)
|
|
114
67
|
elsif server_rendered_html.is_a?(Hash)
|
|
115
|
-
msg =
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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.
|
|
119
72
|
MSG
|
|
120
73
|
raise ReactOnRails::Error, msg
|
|
121
|
-
|
|
122
74
|
else
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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.
|
|
129
86
|
MSG
|
|
130
87
|
raise ReactOnRails::Error, msg
|
|
131
88
|
end
|
|
@@ -136,7 +93,7 @@ module ReactOnRails
|
|
|
136
93
|
# It is exactly like react_component except for the following:
|
|
137
94
|
# 1. prerender: true is automatically added, as this method doesn't make sense for client only
|
|
138
95
|
# rendering.
|
|
139
|
-
# 2. Your JavaScript for server rendering must return an Object
|
|
96
|
+
# 2. Your JavaScript render function for server rendering must return an Object rather than a React component.
|
|
140
97
|
# 3. Your view code must expect an object and not a string.
|
|
141
98
|
#
|
|
142
99
|
# Here is an example of the view code:
|
|
@@ -166,10 +123,12 @@ module ReactOnRails
|
|
|
166
123
|
render_options: internal_result[:render_options]
|
|
167
124
|
)
|
|
168
125
|
else
|
|
169
|
-
msg =
|
|
170
|
-
|
|
126
|
+
msg = <<~MSG
|
|
127
|
+
render function used by react_component_hash for #{component_name} is expected to return
|
|
171
128
|
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.
|
|
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.
|
|
173
132
|
MSG
|
|
174
133
|
raise ReactOnRails::Error, msg
|
|
175
134
|
end
|
|
@@ -178,6 +137,9 @@ module ReactOnRails
|
|
|
178
137
|
# Separate initialization of store from react_component allows multiple react_component calls to
|
|
179
138
|
# use the same Redux store.
|
|
180
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
|
+
#
|
|
181
143
|
# store_name: name of the store, corresponding to your call to ReactOnRails.registerStores in your
|
|
182
144
|
# JavaScript code.
|
|
183
145
|
# props: Ruby Hash or JSON string which contains the properties to pass to the redux store.
|
|
@@ -206,6 +168,7 @@ module ReactOnRails
|
|
|
206
168
|
# that contains a data props.
|
|
207
169
|
def redux_store_hydration_data
|
|
208
170
|
return if @registered_stores_defer_render.blank?
|
|
171
|
+
|
|
209
172
|
@registered_stores_defer_render.reduce("".dup) do |accum, redux_store_data|
|
|
210
173
|
accum << render_redux_store_data(redux_store_data)
|
|
211
174
|
end.html_safe
|
|
@@ -265,6 +228,7 @@ module ReactOnRails
|
|
|
265
228
|
|
|
266
229
|
def json_safe_and_pretty(hash_or_string)
|
|
267
230
|
return "{}" if hash_or_string.nil?
|
|
231
|
+
|
|
268
232
|
unless hash_or_string.is_a?(String) || hash_or_string.is_a?(Hash)
|
|
269
233
|
raise ReactOnRails::Error, "#{__method__} only accepts String or Hash as argument "\
|
|
270
234
|
"(#{hash_or_string.class} given)."
|
|
@@ -275,6 +239,60 @@ module ReactOnRails
|
|
|
275
239
|
ReactOnRails::JsonOutput.escape(json_value)
|
|
276
240
|
end
|
|
277
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
|
+
|
|
278
296
|
private
|
|
279
297
|
|
|
280
298
|
def build_react_component_result_for_server_rendered_string(
|
|
@@ -284,9 +302,15 @@ module ReactOnRails
|
|
|
284
302
|
render_options: required("render_options")
|
|
285
303
|
)
|
|
286
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
|
|
287
311
|
content_tag_options[:id] = render_options.dom_id
|
|
288
312
|
|
|
289
|
-
rendered_output = content_tag(
|
|
313
|
+
rendered_output = content_tag(content_tag_options_html_tag.to_sym,
|
|
290
314
|
server_rendered_html.html_safe,
|
|
291
315
|
content_tag_options)
|
|
292
316
|
|
|
@@ -334,13 +358,11 @@ module ReactOnRails
|
|
|
334
358
|
|
|
335
359
|
def compose_react_component_html_with_spec_and_console(component_specification_tag, rendered_output, console_script)
|
|
336
360
|
# IMPORTANT: Ensure that we mark string as html_safe to avoid escaping.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
#{
|
|
340
|
-
|
|
341
|
-
#{console_script}
|
|
361
|
+
<<~HTML.html_safe
|
|
362
|
+
#{rendered_output}
|
|
363
|
+
#{component_specification_tag}
|
|
364
|
+
#{console_script}
|
|
342
365
|
HTML
|
|
343
|
-
# rubocop:enable Layout/IndentHeredoc
|
|
344
366
|
end
|
|
345
367
|
|
|
346
368
|
# prepend the rails_context if not yet applied
|
|
@@ -405,9 +427,7 @@ module ReactOnRails
|
|
|
405
427
|
|
|
406
428
|
# Returns object with values that are NOT html_safe!
|
|
407
429
|
def server_rendered_react_component(render_options)
|
|
408
|
-
|
|
409
|
-
return { "html" => "", "consoleReplayScript" => "" }
|
|
410
|
-
end
|
|
430
|
+
return { "html" => "", "consoleReplayScript" => "" } unless render_options.prerender
|
|
411
431
|
|
|
412
432
|
react_component_name = render_options.react_component_name
|
|
413
433
|
props = render_options.props
|
|
@@ -433,22 +453,13 @@ module ReactOnRails
|
|
|
433
453
|
#
|
|
434
454
|
# Read more here: http://timelessrepo.com/json-isnt-a-javascript-subset
|
|
435
455
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
(
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
name: '#{react_component_name}',
|
|
444
|
-
domNodeId: '#{render_options.dom_id}',
|
|
445
|
-
props: props,
|
|
446
|
-
trace: #{render_options.trace},
|
|
447
|
-
railsContext: railsContext
|
|
448
|
-
});
|
|
449
|
-
})()
|
|
450
|
-
JS
|
|
451
|
-
# 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
|
+
)
|
|
452
463
|
|
|
453
464
|
begin
|
|
454
465
|
result = ReactOnRails::ServerRenderingPool.server_render_js_with_console_logging(js_code, render_options)
|
|
@@ -480,6 +491,7 @@ module ReactOnRails
|
|
|
480
491
|
JS
|
|
481
492
|
|
|
482
493
|
return result unless @registered_stores.present? || @registered_stores_defer_render.present?
|
|
494
|
+
|
|
483
495
|
declarations = "var reduxProps, store, storeGenerator;\n".dup
|
|
484
496
|
all_stores = (@registered_stores || []) + (@registered_stores_defer_render || [])
|
|
485
497
|
|
|
@@ -496,73 +508,10 @@ module ReactOnRails
|
|
|
496
508
|
result
|
|
497
509
|
end
|
|
498
510
|
|
|
499
|
-
# This is the definitive list of the default values used for the rails_context, which is the
|
|
500
|
-
# second parameter passed to both component and store generator functions.
|
|
501
|
-
# rubocop:disable Metrics/AbcSize
|
|
502
|
-
def rails_context(server_side: required("server_side"))
|
|
503
|
-
@rails_context ||= begin
|
|
504
|
-
result = {
|
|
505
|
-
railsEnv: Rails.env,
|
|
506
|
-
inMailer: in_mailer?,
|
|
507
|
-
# Locale settings
|
|
508
|
-
i18nLocale: I18n.locale,
|
|
509
|
-
i18nDefaultLocale: I18n.default_locale,
|
|
510
|
-
rorVersion: ReactOnRails::VERSION,
|
|
511
|
-
rorPro: ReactOnRails::Utils.react_on_rails_pro?
|
|
512
|
-
}
|
|
513
|
-
if defined?(request) && request.present?
|
|
514
|
-
# Check for encoding of the request's original_url and try to force-encoding the
|
|
515
|
-
# URLs as UTF-8. This situation can occur in browsers that do not encode the
|
|
516
|
-
# entire URL as UTF-8 already, mostly on the Windows platform (IE11 and lower).
|
|
517
|
-
original_url_normalized = request.original_url
|
|
518
|
-
if original_url_normalized.encoding.to_s == "ASCII-8BIT"
|
|
519
|
-
original_url_normalized = original_url_normalized.force_encoding("ISO-8859-1").encode("UTF-8")
|
|
520
|
-
end
|
|
521
|
-
|
|
522
|
-
# Using Addressable instead of standard URI to better deal with
|
|
523
|
-
# non-ASCII characters (see https://github.com/shakacode/react_on_rails/pull/405)
|
|
524
|
-
uri = Addressable::URI.parse(original_url_normalized)
|
|
525
|
-
# uri = Addressable::URI.parse("http://foo.com:3000/posts?id=30&limit=5#time=1305298413")
|
|
526
|
-
|
|
527
|
-
result.merge!(
|
|
528
|
-
# URL settings
|
|
529
|
-
href: uri.to_s,
|
|
530
|
-
location: "#{uri.path}#{uri.query.present? ? "?#{uri.query}" : ''}",
|
|
531
|
-
scheme: uri.scheme, # http
|
|
532
|
-
host: uri.host, # foo.com
|
|
533
|
-
port: uri.port,
|
|
534
|
-
pathname: uri.path, # /posts
|
|
535
|
-
search: uri.query, # id=30&limit=5
|
|
536
|
-
httpAcceptLanguage: request.env["HTTP_ACCEPT_LANGUAGE"]
|
|
537
|
-
)
|
|
538
|
-
end
|
|
539
|
-
if ReactOnRails.configuration.rendering_extension
|
|
540
|
-
custom_context = ReactOnRails.configuration.rendering_extension.custom_context(self)
|
|
541
|
-
result.merge!(custom_context) if custom_context
|
|
542
|
-
end
|
|
543
|
-
result
|
|
544
|
-
end
|
|
545
|
-
|
|
546
|
-
@rails_context.merge(serverSide: server_side)
|
|
547
|
-
end
|
|
548
|
-
|
|
549
|
-
# rubocop:enable Metrics/AbcSize
|
|
550
|
-
|
|
551
511
|
def replay_console_option(val)
|
|
552
512
|
val.nil? ? ReactOnRails.configuration.replay_console : val
|
|
553
513
|
end
|
|
554
514
|
|
|
555
|
-
def use_hot_reloading?
|
|
556
|
-
ENV["REACT_ON_RAILS_ENV"] == "HOT"
|
|
557
|
-
end
|
|
558
|
-
|
|
559
|
-
def send_tag_method(tag_method_name, args)
|
|
560
|
-
asset_type = use_hot_reloading? ? :hot : :static
|
|
561
|
-
assets = Array(args[asset_type])
|
|
562
|
-
options = args.delete_if { |key, _value| %i[hot static].include?(key) }
|
|
563
|
-
send(tag_method_name, *assets, options) unless assets.empty?
|
|
564
|
-
end
|
|
565
|
-
|
|
566
515
|
def in_mailer?
|
|
567
516
|
return false unless defined?(controller)
|
|
568
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,142 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
|
|
5
|
+
module ReactOnRails
|
|
6
|
+
module Locales
|
|
7
|
+
class Base
|
|
8
|
+
def initialize
|
|
9
|
+
return if i18n_dir.nil?
|
|
10
|
+
return unless obsolete?
|
|
11
|
+
|
|
12
|
+
@translations, @defaults = generate_translations
|
|
13
|
+
convert
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def file_format; end
|
|
19
|
+
|
|
20
|
+
def obsolete?
|
|
21
|
+
return true if exist_files.empty?
|
|
22
|
+
|
|
23
|
+
files_are_outdated
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def exist_files
|
|
27
|
+
@exist_files ||= files.select(&File.method(:exist?))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def files_are_outdated
|
|
31
|
+
latest_yml = locale_files.map(&File.method(:mtime)).max
|
|
32
|
+
earliest = exist_files.map(&File.method(:mtime)).min
|
|
33
|
+
latest_yml > earliest
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def file_names
|
|
37
|
+
%w[translations default]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def files
|
|
41
|
+
@files ||= file_names.map { |n| file(n) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def file(name)
|
|
45
|
+
"#{i18n_dir}/#{name}.#{file_format}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def locale_files
|
|
49
|
+
@locale_files ||= begin
|
|
50
|
+
if i18n_yml_dir.present?
|
|
51
|
+
Dir["#{i18n_yml_dir}/**/*.yml"]
|
|
52
|
+
else
|
|
53
|
+
ReactOnRails::Utils.truthy_presence(
|
|
54
|
+
Rails.application && Rails.application.config.i18n.load_path
|
|
55
|
+
).presence
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def i18n_dir
|
|
61
|
+
@i18n_dir ||= ReactOnRails.configuration.i18n_dir
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def i18n_yml_dir
|
|
65
|
+
@i18n_yml_dir ||= ReactOnRails.configuration.i18n_yml_dir
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def default_locale
|
|
69
|
+
@default_locale ||= I18n.default_locale.to_s || "en"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def convert
|
|
73
|
+
file_names.each do |name|
|
|
74
|
+
template = send("template_#{name}")
|
|
75
|
+
path = file(name)
|
|
76
|
+
generate_file(template, path)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def generate_file(template, path)
|
|
81
|
+
result = ERB.new(template).result()
|
|
82
|
+
File.open(path, "w") do |f|
|
|
83
|
+
f.write(result)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def generate_translations
|
|
88
|
+
translations = {}
|
|
89
|
+
defaults = {}
|
|
90
|
+
locale_files.each do |f|
|
|
91
|
+
translation = YAML.safe_load(File.open(f))
|
|
92
|
+
key = translation.keys[0]
|
|
93
|
+
val = flatten(translation[key])
|
|
94
|
+
translations = translations.deep_merge(key => val)
|
|
95
|
+
defaults = defaults.deep_merge(flatten_defaults(val)) if key == default_locale
|
|
96
|
+
end
|
|
97
|
+
[translations.to_json, defaults.to_json]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def format(input)
|
|
101
|
+
input.to_s.tr(".", "_").camelize(:lower).to_sym
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def flatten_defaults(val)
|
|
105
|
+
flatten(val).each_with_object({}) do |(k, v), h|
|
|
106
|
+
key = format(k)
|
|
107
|
+
h[key] = { id: k, defaultMessage: v }
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def flatten(translations)
|
|
112
|
+
translations.each_with_object({}) do |(k, v), h|
|
|
113
|
+
if v.is_a? Hash
|
|
114
|
+
flatten(v).map { |hk, hv| h["#{k}.#{hk}".to_sym] = hv }
|
|
115
|
+
elsif v.is_a?(String)
|
|
116
|
+
h[k] = v.gsub("%{", "{")
|
|
117
|
+
elsif !v.is_a?(Array)
|
|
118
|
+
h[k] = v
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def template_translations
|
|
124
|
+
<<-JS.strip_heredoc
|
|
125
|
+
export const translations = #{@translations};
|
|
126
|
+
JS
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def template_default
|
|
130
|
+
<<-JS.strip_heredoc
|
|
131
|
+
import { defineMessages } from 'react-intl';
|
|
132
|
+
|
|
133
|
+
const defaultLocale = \'#{default_locale}\';
|
|
134
|
+
|
|
135
|
+
const defaultMessages = defineMessages(#{@defaults});
|
|
136
|
+
|
|
137
|
+
export { defaultMessages, defaultLocale };
|
|
138
|
+
JS
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|