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