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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +14 -2
- data/CONTRIBUTING.md +0 -7
- data/Gemfile +8 -13
- data/README.md +5 -4
- data/app/helpers/react_on_rails_helper.rb +1 -534
- data/docs/additional-reading/caching-and-performance.md +2 -29
- data/docs/additional-reading/server-rendering-tips.md +4 -4
- data/docs/basics/configuration.md +23 -9
- data/docs/basics/i18n.md +4 -0
- data/lib/generators/react_on_rails/base_generator.rb +2 -3
- data/lib/generators/react_on_rails/dev_tests_generator.rb +1 -1
- data/lib/generators/react_on_rails/install_generator.rb +1 -1
- data/lib/generators/react_on_rails/react_no_redux_generator.rb +1 -1
- data/lib/generators/react_on_rails/react_with_redux_generator.rb +1 -1
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb +2 -1
- data/lib/generators/react_on_rails/templates/dev_tests/spec/simplecov_helper.rb +2 -2
- data/lib/react_on_rails.rb +3 -2
- data/lib/react_on_rails/configuration.rb +27 -8
- data/lib/react_on_rails/error.rb +4 -0
- data/lib/react_on_rails/prerender_error.rb +1 -1
- data/lib/react_on_rails/react_on_rails_helper.rb +546 -0
- data/lib/react_on_rails/server_rendering_pool.rb +21 -11
- data/lib/react_on_rails/server_rendering_pool/{exec.rb → ruby_embedded_java_script.rb} +35 -38
- data/lib/react_on_rails/test_helper.rb +1 -1
- data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +2 -2
- data/lib/react_on_rails/utils.rb +12 -73
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_checker.rb +4 -2
- data/lib/react_on_rails/webpacker_utils.rb +42 -0
- data/package.json +1 -1
- data/rakelib/example_type.rb +1 -1
- data/rakelib/examples.rake +1 -1
- data/rakelib/release.rake +0 -5
- data/rakelib/task_helpers.rb +1 -1
- data/react_on_rails.gemspec +12 -9
- metadata +30 -13
- data/lib/react_on_rails/server_rendering_pool/node.rb +0 -81
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2ab30d4fd951ada88bca651522969b8e0e11f2cb
|
4
|
+
data.tar.gz: b3e8aee757c36eb7c33e50334e6d1314e96983ef
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 194cf31c9a5e2ea90e82ced1edc653f8c795ac5f29d8af01b4a3eb53a174b07c1453b73d041f60d16bd36c758cf308c42c970525f2b6a600b7dfc6da3c7c8fe9
|
7
|
+
data.tar.gz: 1f7ae2505fb7e88bb1e2906302d57866e11c7432040046c3638e1f91f78c8c8e7438ef6b69376c8e3a87ff66bca53029e1c18658470f7ad6f05c510b688f34b6
|
data/.rubocop.yml
CHANGED
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"
|
14
|
+
gem "jbuilder"
|
14
15
|
gem "jquery-rails"
|
15
16
|
gem "mini_racer"
|
16
17
|
gem "puma"
|
17
|
-
|
18
|
+
|
18
19
|
gem "rails_12factor"
|
19
|
-
gem "rubocop", "~> 0.50", require: false
|
20
20
|
gem "ruby-lint", require: false
|
21
|
-
gem "sass-rails"
|
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"
|
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"
|
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.
|
38
|
-
5.
|
39
|
-
6.
|
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 [
|
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::
|
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
|