react_on_rails 11.0.5 → 13.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.circleci/config.yml +338 -0
- data/.eslintignore +2 -1
- data/.eslintrc +32 -3
- data/.github/FUNDING.yml +1 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +23 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +19 -0
- data/.github/workflows/lint-js-and-ruby.yml +54 -0
- data/.github/workflows/main.yml +183 -0
- data/.github/workflows/package-js-tests.yml +35 -0
- data/.github/workflows/rspec-package-specs.yml +46 -0
- data/.gitignore +3 -4
- data/.prettierignore +14 -0
- data/.prettierrc +20 -0
- data/.rubocop.yml +76 -34
- data/.travis.yml +15 -22
- data/CHANGELOG.md +443 -55
- data/CONTRIBUTING.md +62 -80
- data/Gemfile +1 -35
- data/Gemfile.development_dependencies +50 -0
- data/KUDOS.md +4 -1
- data/{docs/LICENSE.md → LICENSE.md} +1 -1
- data/NEWS.md +14 -4
- data/REACT-ON-RAILS-PRO-LICENSE +95 -0
- data/README.md +107 -802
- data/Rakefile +1 -8
- data/SUMMARY.md +51 -29
- data/book.json +5 -5
- data/docs/{basics/generator.md → additional-details/generator-details.md} +5 -13
- data/docs/{basics/installation-overview.md → additional-details/manual-installation-overview.md} +9 -14
- data/docs/{basics → additional-details}/migrating-from-react-rails.md +1 -1
- data/docs/additional-details/recommended-project-structure.md +69 -0
- data/docs/additional-details/tips-for-usage-with-sp6.md +15 -0
- data/docs/additional-details/upgrade-webpacker-v3-to-v4.md +10 -0
- data/docs/api/javascript-api.md +35 -6
- data/docs/api/redux-store-api.md +102 -0
- data/docs/api/view-helpers-api.md +133 -0
- data/docs/contributor-info/errors-with-hooks.md +45 -0
- data/docs/contributor-info/linters.md +5 -6
- data/docs/contributor-info/pull-requests.md +42 -0
- data/docs/contributor-info/releasing.md +1 -1
- data/docs/deployment/heroku-deployment.md +39 -0
- data/docs/getting-started.md +196 -0
- data/docs/guides/client-vs-server-rendering.md +27 -0
- data/docs/guides/configuration.md +289 -0
- data/docs/guides/deployment.md +5 -0
- data/docs/guides/file-system-based-automated-bundle-generation.md +197 -0
- data/docs/guides/hmr-and-hot-reloading-with-the-webpack-dev-server.md +104 -0
- data/docs/guides/how-react-on-rails-works.md +44 -0
- data/docs/guides/how-to-conditionally-server-render-based-on-device-type.md +40 -0
- data/docs/guides/how-to-use-different-files-for-client-and-server-rendering.md +98 -0
- data/docs/guides/i18n.md +87 -0
- data/docs/guides/installation-into-an-existing-rails-app.md +66 -0
- data/docs/guides/minitest-configuration.md +31 -0
- data/docs/guides/rails-webpacker-react-integration-options.md +213 -0
- data/docs/guides/react-on-rails-overview.md +29 -0
- data/docs/guides/react-server-rendering.md +32 -0
- data/docs/guides/render-functions-and-railscontext.md +205 -0
- data/docs/guides/rspec-configuration.md +73 -0
- data/docs/guides/tutorial.md +371 -0
- data/docs/{basics → guides}/upgrading-react-on-rails.md +126 -3
- data/docs/guides/webpack-configuration.md +42 -0
- data/docs/home.md +23 -0
- data/docs/javascript/asset-pipeline.md +12 -0
- data/docs/{additional-reading → javascript}/code-splitting.md +21 -11
- data/docs/javascript/converting-from-custom-webpack-config-to-rails-webpacker-config.md +10 -0
- data/docs/javascript/credits.md +10 -0
- data/docs/{additional-reading → javascript}/images.md +5 -6
- data/docs/javascript/react-helmet.md +100 -0
- data/docs/javascript/react-router.md +90 -0
- data/docs/{additional-reading → javascript}/server-rendering-tips.md +15 -12
- data/docs/javascript/troubleshooting-when-using-shakapacker.md +77 -0
- data/docs/{additional-reading → javascript}/webpack.md +2 -2
- data/docs/misc/articles.md +20 -0
- data/docs/misc/doctrine.md +5 -6
- data/docs/outdated/deferred-rendering.md +39 -0
- data/docs/{additional-reading → outdated}/rails-assets-relative-paths.md +4 -4
- data/docs/{additional-reading → outdated}/rails-assets.md +12 -20
- data/docs/{misc → outdated}/rails3.md +2 -2
- data/docs/rails/convert-rails-5-api-only-app.md +19 -0
- data/docs/rails/rails-engine-integration.md +32 -0
- data/docs/{additional-reading → rails}/rails_view_rendering_from_inline_javascript.md +2 -1
- data/docs/{additional-reading → rails}/turbolinks.md +13 -1
- data/docs/react-on-rails-pro/react-on-rails-pro.md +43 -0
- data/docs/testimonials/hvmn.md +25 -0
- data/docs/testimonials/resortpass.md +13 -0
- data/docs/testimonials/testimonials.md +28 -0
- data/jest.config.js +4 -0
- data/lib/generators/USAGE +1 -1
- data/lib/generators/react_on_rails/adapt_for_older_shakapacker_generator.rb +41 -0
- data/lib/generators/react_on_rails/base_generator.rb +55 -43
- data/lib/generators/react_on_rails/bin/dev +30 -0
- data/lib/generators/react_on_rails/bin/dev-static +30 -0
- data/lib/generators/react_on_rails/dev_tests_generator.rb +4 -3
- data/lib/generators/react_on_rails/generator_helper.rb +8 -6
- data/lib/generators/react_on_rails/generator_messages.rb +40 -0
- data/lib/generators/react_on_rails/install_generator.rb +37 -0
- data/lib/generators/react_on_rails/templates/.eslintrc +3 -1
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev +4 -6
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static +9 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +21 -40
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +4 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorldServer.js +5 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/server-bundle.js +8 -0
- data/lib/generators/react_on_rails/templates/base/base/app/views/layouts/hello_world.html.erb +2 -1
- data/lib/generators/react_on_rails/templates/base/base/babel.config.js.tt +32 -0
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb +20 -4
- data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +62 -0
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/clientWebpackConfig.js.tt +17 -0
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +17 -0
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +25 -0
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/production.js.tt +9 -0
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +117 -0
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/test.js.tt +9 -0
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/webpack.config.js.tt +15 -0
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/webpackConfig.js.tt +36 -0
- data/lib/generators/react_on_rails/templates/dev_tests/spec/rails_helper.rb +8 -2
- data/lib/generators/react_on_rails/templates/dev_tests/spec/simplecov_helper.rb +1 -1
- data/lib/generators/react_on_rails/templates/dev_tests/spec/{features → system}/hello_world_spec.rb +2 -2
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +6 -9
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.js +1 -3
- data/lib/react_on_rails/configuration.rb +198 -145
- data/lib/react_on_rails/error.rb +2 -0
- data/lib/react_on_rails/git_utils.rb +5 -3
- data/lib/react_on_rails/{react_on_rails_helper.rb → helper.rb} +201 -190
- data/lib/react_on_rails/json_output.rb +1 -1
- data/lib/react_on_rails/json_parse_error.rb +28 -0
- data/lib/react_on_rails/locales/base.rb +169 -0
- data/lib/react_on_rails/locales/to_js.rb +33 -0
- data/lib/react_on_rails/locales/to_json.rb +23 -0
- data/lib/react_on_rails/packs_generator.rb +234 -0
- data/lib/react_on_rails/prerender_error.rb +35 -27
- data/lib/react_on_rails/react_component/render_options.rb +64 -9
- data/lib/react_on_rails/server_rendering_js_code.rb +55 -0
- data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +126 -76
- data/lib/react_on_rails/server_rendering_pool.rb +0 -1
- data/lib/react_on_rails/test_helper/ensure_assets_compiled.rb +9 -8
- data/lib/react_on_rails/test_helper/webpack_assets_compiler.rb +17 -0
- data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +13 -12
- data/lib/react_on_rails/test_helper.rb +24 -3
- data/lib/react_on_rails/utils.rb +94 -25
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_checker.rb +5 -1
- data/lib/react_on_rails/version_syntax_converter.rb +14 -12
- data/lib/react_on_rails/webpacker_utils.rb +105 -5
- data/lib/react_on_rails.rb +8 -2
- data/lib/tasks/assets.rake +28 -60
- data/lib/tasks/generate_packs.rake +11 -0
- data/lib/tasks/locale.rake +5 -4
- data/package-scripts.yml +49 -0
- data/package.json +52 -47
- data/rakelib/docker.rake +0 -5
- data/rakelib/dummy_apps.rake +5 -8
- data/rakelib/example_type.rb +12 -3
- data/rakelib/examples.rake +5 -4
- data/rakelib/lint.rake +5 -16
- data/rakelib/node_package.rake +2 -2
- data/rakelib/release.rake +37 -23
- data/rakelib/run_rspec.rake +16 -44
- data/rakelib/task_helpers.rb +16 -4
- data/react_on_rails.gemspec +6 -22
- data/tsconfig.json +14 -0
- data/webpackConfigLoader.js +5 -4
- data/yarn.lock +5935 -3106
- metadata +122 -272
- data/Gemfile.rails32 +0 -74
- data/docs/additional-reading/asset-pipeline.md +0 -20
- data/docs/additional-reading/babel.md +0 -5
- data/docs/additional-reading/caching-and-performance.md +0 -4
- data/docs/additional-reading/heroku-deployment.md +0 -92
- data/docs/additional-reading/hot-reloading-rails-development.md +0 -57
- data/docs/additional-reading/node-server-rendering.md +0 -5
- data/docs/additional-reading/rails-engine-integration.md +0 -34
- data/docs/additional-reading/react-helmet.md +0 -80
- data/docs/additional-reading/react-router.md +0 -113
- data/docs/additional-reading/recommended-project-structure.md +0 -49
- data/docs/additional-reading/rspec-configuration.md +0 -56
- data/docs/additional-reading/webpack-dev-server.md +0 -15
- data/docs/api/ruby-api-hot-reload-view-helpers.md +0 -44
- data/docs/api/ruby-api.md +0 -8
- data/docs/basics/configuration.md +0 -163
- data/docs/basics/i18n.md +0 -77
- data/docs/tutorial.md +0 -220
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-server +0 -12
- data/lib/react_on_rails/assets_precompile.rb +0 -150
- data/lib/react_on_rails/locales_to_js.rb +0 -134
- data/ruby-lint.yml +0 -25
- /data/docs/{additional-reading → additional-details}/updating-dependencies.md +0 -0
- /data/docs/{additional-reading → deployment}/elastic-beanstalk.md +0 -0
- /data/docs/{additional-reading → javascript}/angular-js-integration-migration.md +0 -0
- /data/docs/{additional-reading → javascript}/capistrano-deployment.md +0 -0
- /data/docs/{additional-reading → javascript}/foreman-issues.md +0 -0
- /data/docs/{additional-reading → javascript}/node-dependencies-and-npm.md +0 -0
- /data/docs/{additional-reading → javascript}/react-and-redux.md +0 -0
- /data/docs/{additional-reading → javascript}/troubleshooting-when-using-webpacker.md +0 -0
- /data/docs/{additional-reading → javascript}/webpack-v1-notes.md +0 -0
- /data/docs/{coding-style → misc}/style.md +0 -0
- /data/docs/{additional-reading → misc}/tips.md +0 -0
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# rubocop:disable Metrics/ModuleLength
|
4
|
+
# rubocop:disable Metrics/MethodLength
|
4
5
|
# NOTE:
|
5
6
|
# For any heredoc JS:
|
6
7
|
# 1. The white spacing in this file matters!
|
@@ -15,73 +16,25 @@ module ReactOnRails
|
|
15
16
|
module Helper
|
16
17
|
include ReactOnRails::Utils::Required
|
17
18
|
|
18
|
-
COMPONENT_HTML_KEY = "componentHtml"
|
19
|
+
COMPONENT_HTML_KEY = "componentHtml"
|
19
20
|
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
21
|
+
# react_component_name: can be a React function or class component or a "Render-Function".
|
22
|
+
# "Render-Functions" differ from a React function in that they take two parameters, the
|
23
|
+
# props and the railsContext, like this:
|
23
24
|
#
|
24
|
-
#
|
25
|
-
# these params are optional, and support either a single value, or an array.
|
25
|
+
# let MyReactComponentApp = (props, railsContext) => <MyReactComponent {...props}/>;
|
26
26
|
#
|
27
|
-
#
|
28
|
-
#
|
27
|
+
# Alternately, you can define the Render-Function with an additional property
|
28
|
+
# `.renderFunction = true`:
|
29
29
|
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
# media: 'all',
|
33
|
-
# 'data-turbolinks-track' => "reload") %>
|
30
|
+
# let MyReactComponentApp = (props) => <MyReactComponent {...props}/>;
|
31
|
+
# MyReactComponent.renderFunction = true;
|
34
32
|
#
|
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
33
|
# Exposing the react_component_name is necessary to both a plain ReactComponent as well as
|
81
34
|
# a generator:
|
82
35
|
# See README.md for how to "register" your react components.
|
83
|
-
# See spec/dummy/client/app/
|
84
|
-
# spec/dummy/client/app/
|
36
|
+
# See spec/dummy/client/app/packs/server-bundle.js and
|
37
|
+
# spec/dummy/client/app/packs/client-bundle.js for examples of this.
|
85
38
|
#
|
86
39
|
# options:
|
87
40
|
# props: Ruby Hash or JSON string which contains the properties to pass to the react object. Do
|
@@ -98,33 +51,41 @@ module ReactOnRails
|
|
98
51
|
# raise_on_prerender_error: <true/false> Default to false. True will raise exception on server
|
99
52
|
# if the JS code throws
|
100
53
|
# Any other options are passed to the content tag, including the id.
|
54
|
+
# random_dom_id can be set to override the default from the config/initializers. That's only
|
55
|
+
# used if you have multiple instance of the same component on the Rails view.
|
101
56
|
def react_component(component_name, options = {})
|
102
57
|
internal_result = internal_react_component(component_name, options)
|
103
58
|
server_rendered_html = internal_result[:result]["html"]
|
104
59
|
console_script = internal_result[:result]["consoleReplayScript"]
|
60
|
+
render_options = internal_result[:render_options]
|
105
61
|
|
106
|
-
|
62
|
+
case server_rendered_html
|
63
|
+
when String
|
107
64
|
build_react_component_result_for_server_rendered_string(
|
108
65
|
server_rendered_html: server_rendered_html,
|
109
66
|
component_specification_tag: internal_result[:tag],
|
110
67
|
console_script: console_script,
|
111
|
-
render_options:
|
68
|
+
render_options: render_options
|
112
69
|
)
|
113
|
-
|
114
|
-
msg =
|
115
|
-
|
116
|
-
|
117
|
-
|
70
|
+
when Hash
|
71
|
+
msg = <<~MSG
|
72
|
+
Use react_component_hash (not react_component) to return a Hash to your ruby view code. See
|
73
|
+
https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx
|
74
|
+
for an example of the necessary javascript configuration.
|
118
75
|
MSG
|
119
76
|
raise ReactOnRails::Error, msg
|
120
|
-
|
121
77
|
else
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
78
|
+
class_name = server_rendered_html.class.name
|
79
|
+
msg = <<~MSG
|
80
|
+
ReactOnRails: server_rendered_html is expected to be a String or Hash for #{component_name}.
|
81
|
+
Type is #{class_name}
|
82
|
+
Value:
|
83
|
+
#{server_rendered_html}
|
84
|
+
|
85
|
+
If you're trying to use a Render-Function to return a Hash to your ruby view code, then use
|
86
|
+
react_component_hash instead of react_component and see
|
87
|
+
https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx
|
88
|
+
for an example of the JavaScript code.
|
128
89
|
MSG
|
129
90
|
raise ReactOnRails::Error, msg
|
130
91
|
end
|
@@ -135,7 +96,7 @@ module ReactOnRails
|
|
135
96
|
# It is exactly like react_component except for the following:
|
136
97
|
# 1. prerender: true is automatically added, as this method doesn't make sense for client only
|
137
98
|
# rendering.
|
138
|
-
# 2. Your JavaScript for server rendering must return an Object
|
99
|
+
# 2. Your JavaScript Render-Function for server rendering must return an Object rather than a React component.
|
139
100
|
# 3. Your view code must expect an object and not a string.
|
140
101
|
#
|
141
102
|
# Here is an example of the view code:
|
@@ -152,6 +113,7 @@ module ReactOnRails
|
|
152
113
|
internal_result = internal_react_component(component_name, options)
|
153
114
|
server_rendered_html = internal_result[:result]["html"]
|
154
115
|
console_script = internal_result[:result]["consoleReplayScript"]
|
116
|
+
render_options = internal_result[:render_options]
|
155
117
|
|
156
118
|
if server_rendered_html.is_a?(String) && internal_result[:result]["hasErrors"]
|
157
119
|
server_rendered_html = { COMPONENT_HTML_KEY => internal_result[:result]["html"] }
|
@@ -162,13 +124,15 @@ module ReactOnRails
|
|
162
124
|
server_rendered_html: server_rendered_html,
|
163
125
|
component_specification_tag: internal_result[:tag],
|
164
126
|
console_script: console_script,
|
165
|
-
render_options:
|
127
|
+
render_options: render_options
|
166
128
|
)
|
167
129
|
else
|
168
|
-
msg =
|
169
|
-
|
130
|
+
msg = <<~MSG
|
131
|
+
Render-Function used by react_component_hash for #{component_name} is expected to return
|
170
132
|
an Object. See https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/client/app/startup/ReactHelmetServerApp.jsx
|
171
|
-
for an example of the JavaScript code.
|
133
|
+
for an example of the JavaScript code.
|
134
|
+
Note, your Render-Function must either take 2 params or have the property
|
135
|
+
`.renderFunction = true` added to it to distinguish it from a React Function Component.
|
172
136
|
MSG
|
173
137
|
raise ReactOnRails::Error, msg
|
174
138
|
end
|
@@ -177,6 +141,9 @@ module ReactOnRails
|
|
177
141
|
# Separate initialization of store from react_component allows multiple react_component calls to
|
178
142
|
# use the same Redux store.
|
179
143
|
#
|
144
|
+
# NOTE: This technique not recommended as it prevents dynamic code splitting for performance.
|
145
|
+
# Instead, you should use the standard react_component view helper.
|
146
|
+
#
|
180
147
|
# store_name: name of the store, corresponding to your call to ReactOnRails.registerStores in your
|
181
148
|
# JavaScript code.
|
182
149
|
# props: Ruby Hash or JSON string which contains the properties to pass to the redux store.
|
@@ -189,7 +156,7 @@ module ReactOnRails
|
|
189
156
|
@registered_stores_defer_render ||= []
|
190
157
|
@registered_stores_defer_render << redux_store_data
|
191
158
|
"YOU SHOULD NOT SEE THIS ON YOUR VIEW -- Uses as a code block, like <% redux_store %> "\
|
192
|
-
|
159
|
+
"and not <%= redux store %>"
|
193
160
|
else
|
194
161
|
@registered_stores ||= []
|
195
162
|
@registered_stores << redux_store_data
|
@@ -205,7 +172,8 @@ module ReactOnRails
|
|
205
172
|
# that contains a data props.
|
206
173
|
def redux_store_hydration_data
|
207
174
|
return if @registered_stores_defer_render.blank?
|
208
|
-
|
175
|
+
|
176
|
+
@registered_stores_defer_render.reduce(+"") do |accum, redux_store_data|
|
209
177
|
accum << render_redux_store_data(redux_store_data)
|
210
178
|
end.html_safe
|
211
179
|
end
|
@@ -217,7 +185,7 @@ module ReactOnRails
|
|
217
185
|
# Helper method to take javascript expression and returns the output from evaluating it.
|
218
186
|
# If you have more than one line that needs to be executed, wrap it in an IIFE.
|
219
187
|
# JS exceptions are caught and console messages are handled properly.
|
220
|
-
# Options include:{ prerender:, trace:, raise_on_prerender_error: }
|
188
|
+
# Options include:{ prerender:, trace:, raise_on_prerender_error:, throw_js_errors: }
|
221
189
|
def server_render_js(js_expression, options = {})
|
222
190
|
render_options = ReactOnRails::ReactComponent::RenderOptions
|
223
191
|
.new(react_component_name: "generic-js", options: options)
|
@@ -227,6 +195,8 @@ module ReactOnRails
|
|
227
195
|
var htmlResult = '';
|
228
196
|
var consoleReplayScript = '';
|
229
197
|
var hasErrors = false;
|
198
|
+
var renderingError = null;
|
199
|
+
var renderingErrorObject = {};
|
230
200
|
|
231
201
|
try {
|
232
202
|
htmlResult =
|
@@ -234,9 +204,17 @@ module ReactOnRails
|
|
234
204
|
return #{js_expression};
|
235
205
|
})();
|
236
206
|
} catch(e) {
|
207
|
+
renderingError = e;
|
208
|
+
if (#{render_options.throw_js_errors}) {
|
209
|
+
throw e;
|
210
|
+
}
|
237
211
|
htmlResult = ReactOnRails.handleError({e: e, name: null,
|
238
212
|
jsCode: '#{escape_javascript(js_expression)}', serverSide: true});
|
239
213
|
hasErrors = true;
|
214
|
+
renderingErrorObject = {
|
215
|
+
message: renderingError.message,
|
216
|
+
stack: renderingError.stack,
|
217
|
+
}
|
240
218
|
}
|
241
219
|
|
242
220
|
consoleReplayScript = ReactOnRails.buildConsoleReplay();
|
@@ -244,7 +222,8 @@ module ReactOnRails
|
|
244
222
|
return JSON.stringify({
|
245
223
|
html: htmlResult,
|
246
224
|
consoleReplayScript: consoleReplayScript,
|
247
|
-
hasErrors: hasErrors
|
225
|
+
hasErrors: hasErrors,
|
226
|
+
renderingError: renderingErrorObject
|
248
227
|
});
|
249
228
|
|
250
229
|
})()
|
@@ -257,16 +236,17 @@ module ReactOnRails
|
|
257
236
|
console_log_script = result["consoleLogScript"]
|
258
237
|
raw("#{html}#{render_options.replay_console ? console_log_script : ''}")
|
259
238
|
rescue ExecJS::ProgramError => err
|
260
|
-
raise ReactOnRails::PrerenderError
|
261
|
-
|
262
|
-
|
239
|
+
raise ReactOnRails::PrerenderError.new(component_name: "N/A (server_render_js called)",
|
240
|
+
err: err,
|
241
|
+
js_code: js_code)
|
263
242
|
end
|
264
243
|
|
265
244
|
def json_safe_and_pretty(hash_or_string)
|
266
245
|
return "{}" if hash_or_string.nil?
|
267
|
-
|
246
|
+
|
247
|
+
unless hash_or_string.is_a?(String) || hash_or_string.is_a?(Hash)
|
268
248
|
raise ReactOnRails::Error, "#{__method__} only accepts String or Hash as argument "\
|
269
|
-
|
249
|
+
"(#{hash_or_string.class} given)."
|
270
250
|
end
|
271
251
|
|
272
252
|
json_value = hash_or_string.is_a?(String) ? hash_or_string : hash_or_string.to_json
|
@@ -274,8 +254,86 @@ module ReactOnRails
|
|
274
254
|
ReactOnRails::JsonOutput.escape(json_value)
|
275
255
|
end
|
276
256
|
|
257
|
+
# This is the definitive list of the default values used for the rails_context, which is the
|
258
|
+
# second parameter passed to both component and store Render-Functions.
|
259
|
+
# This method can be called from views and from the controller, as `helpers.rails_context`
|
260
|
+
#
|
261
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
262
|
+
def rails_context(server_side: true)
|
263
|
+
# ALERT: Keep in sync with node_package/src/types/index.ts for the properties of RailsContext
|
264
|
+
@rails_context ||= begin
|
265
|
+
result = {
|
266
|
+
railsEnv: Rails.env,
|
267
|
+
inMailer: in_mailer?,
|
268
|
+
# Locale settings
|
269
|
+
i18nLocale: I18n.locale,
|
270
|
+
i18nDefaultLocale: I18n.default_locale,
|
271
|
+
rorVersion: ReactOnRails::VERSION,
|
272
|
+
# TODO: v13 just use the version if existing
|
273
|
+
rorPro: ReactOnRails::Utils.react_on_rails_pro?
|
274
|
+
}
|
275
|
+
if ReactOnRails::Utils.react_on_rails_pro?
|
276
|
+
result[:rorProVersion] = ReactOnRails::Utils.react_on_rails_pro_version
|
277
|
+
end
|
278
|
+
|
279
|
+
if defined?(request) && request.present?
|
280
|
+
# Check for encoding of the request's original_url and try to force-encoding the
|
281
|
+
# URLs as UTF-8. This situation can occur in browsers that do not encode the
|
282
|
+
# entire URL as UTF-8 already, mostly on the Windows platform (IE11 and lower).
|
283
|
+
original_url_normalized = request.original_url
|
284
|
+
if original_url_normalized.encoding == Encoding::BINARY
|
285
|
+
original_url_normalized = original_url_normalized.force_encoding(Encoding::ISO_8859_1)
|
286
|
+
.encode(Encoding::UTF_8)
|
287
|
+
end
|
288
|
+
|
289
|
+
# Using Addressable instead of standard URI to better deal with
|
290
|
+
# non-ASCII characters (see https://github.com/shakacode/react_on_rails/pull/405)
|
291
|
+
uri = Addressable::URI.parse(original_url_normalized)
|
292
|
+
# uri = Addressable::URI.parse("http://foo.com:3000/posts?id=30&limit=5#time=1305298413")
|
293
|
+
|
294
|
+
result.merge!(
|
295
|
+
# URL settings
|
296
|
+
href: uri.to_s,
|
297
|
+
location: "#{uri.path}#{uri.query.present? ? "?#{uri.query}" : ''}",
|
298
|
+
scheme: uri.scheme, # http
|
299
|
+
host: uri.host, # foo.com
|
300
|
+
port: uri.port,
|
301
|
+
pathname: uri.path, # /posts
|
302
|
+
search: uri.query, # id=30&limit=5
|
303
|
+
httpAcceptLanguage: request.env["HTTP_ACCEPT_LANGUAGE"]
|
304
|
+
)
|
305
|
+
end
|
306
|
+
if ReactOnRails.configuration.rendering_extension
|
307
|
+
custom_context = ReactOnRails.configuration.rendering_extension.custom_context(self)
|
308
|
+
result.merge!(custom_context) if custom_context
|
309
|
+
end
|
310
|
+
result
|
311
|
+
end
|
312
|
+
|
313
|
+
@rails_context.merge(serverSide: server_side)
|
314
|
+
end
|
315
|
+
|
316
|
+
def load_pack_for_generated_component(react_component_name, render_options)
|
317
|
+
return unless render_options.auto_load_bundle
|
318
|
+
|
319
|
+
ReactOnRails::WebpackerUtils.raise_nested_entries_disabled unless ReactOnRails::WebpackerUtils.nested_entries?
|
320
|
+
if Rails.env.development?
|
321
|
+
is_component_pack_present = File.exist?(generated_components_pack_path(react_component_name))
|
322
|
+
raise_missing_autoloaded_bundle(react_component_name) unless is_component_pack_present
|
323
|
+
end
|
324
|
+
append_javascript_pack_tag("generated/#{react_component_name}",
|
325
|
+
defer: ReactOnRails.configuration.defer_generated_component_packs)
|
326
|
+
append_stylesheet_pack_tag("generated/#{react_component_name}")
|
327
|
+
end
|
328
|
+
|
329
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
330
|
+
|
277
331
|
private
|
278
332
|
|
333
|
+
def generated_components_pack_path(react_component_name)
|
334
|
+
"#{ReactOnRails::WebpackerUtils.webpacker_source_entry_path}/generated/#{react_component_name}.js"
|
335
|
+
end
|
336
|
+
|
279
337
|
def build_react_component_result_for_server_rendered_string(
|
280
338
|
server_rendered_html: required("server_rendered_html"),
|
281
339
|
component_specification_tag: required("component_specification_tag"),
|
@@ -283,9 +341,15 @@ module ReactOnRails
|
|
283
341
|
render_options: required("render_options")
|
284
342
|
)
|
285
343
|
content_tag_options = render_options.html_options
|
344
|
+
if content_tag_options.key?(:tag)
|
345
|
+
content_tag_options_html_tag = content_tag_options[:tag]
|
346
|
+
content_tag_options.delete(:tag)
|
347
|
+
else
|
348
|
+
content_tag_options_html_tag = "div"
|
349
|
+
end
|
286
350
|
content_tag_options[:id] = render_options.dom_id
|
287
351
|
|
288
|
-
rendered_output = content_tag(
|
352
|
+
rendered_output = content_tag(content_tag_options_html_tag.to_sym,
|
289
353
|
server_rendered_html.html_safe,
|
290
354
|
content_tag_options)
|
291
355
|
|
@@ -333,13 +397,11 @@ module ReactOnRails
|
|
333
397
|
|
334
398
|
def compose_react_component_html_with_spec_and_console(component_specification_tag, rendered_output, console_script)
|
335
399
|
# IMPORTANT: Ensure that we mark string as html_safe to avoid escaping.
|
336
|
-
|
337
|
-
|
338
|
-
#{
|
339
|
-
|
340
|
-
#{console_script}
|
400
|
+
<<~HTML.html_safe
|
401
|
+
#{rendered_output}
|
402
|
+
#{component_specification_tag}
|
403
|
+
#{console_script}
|
341
404
|
HTML
|
342
|
-
# rubocop:enable Layout/IndentHeredoc
|
343
405
|
end
|
344
406
|
|
345
407
|
# prepend the rails_context if not yet applied
|
@@ -372,7 +434,7 @@ module ReactOnRails
|
|
372
434
|
# Setup the page_loaded_js, which is the same regardless of prerendering or not!
|
373
435
|
# The reason is that React is smart about not doing extra work if the server rendering did its job.
|
374
436
|
component_specification_tag = content_tag(:script,
|
375
|
-
json_safe_and_pretty(render_options.
|
437
|
+
json_safe_and_pretty(render_options.client_props).html_safe,
|
376
438
|
type: "application/json",
|
377
439
|
class: "js-react-on-rails-component",
|
378
440
|
"data-component-name" => render_options.react_component_name,
|
@@ -382,6 +444,8 @@ module ReactOnRails
|
|
382
444
|
# Create the HTML rendering part
|
383
445
|
result = server_rendered_react_component(render_options)
|
384
446
|
|
447
|
+
load_pack_for_generated_component(react_component_name, render_options)
|
448
|
+
|
385
449
|
{
|
386
450
|
render_options: render_options,
|
387
451
|
tag: component_specification_tag,
|
@@ -430,56 +494,48 @@ module ReactOnRails
|
|
430
494
|
#
|
431
495
|
# Read more here: http://timelessrepo.com/json-isnt-a-javascript-subset
|
432
496
|
|
433
|
-
|
434
|
-
|
435
|
-
(
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
name: '#{react_component_name}',
|
441
|
-
domNodeId: '#{render_options.dom_id}',
|
442
|
-
props: props,
|
443
|
-
trace: #{render_options.trace},
|
444
|
-
railsContext: railsContext
|
445
|
-
});
|
446
|
-
})()
|
447
|
-
JS
|
448
|
-
# rubocop:enable Layout/IndentHeredoc
|
497
|
+
js_code = ReactOnRails::ServerRenderingJsCode.server_rendering_component_js_code(
|
498
|
+
props_string: props_string(props).gsub("\u2028", '\u2028').gsub("\u2029", '\u2029'),
|
499
|
+
rails_context: rails_context(server_side: true).to_json,
|
500
|
+
redux_stores: initialize_redux_stores,
|
501
|
+
react_component_name: react_component_name,
|
502
|
+
render_options: render_options
|
503
|
+
)
|
449
504
|
|
450
505
|
begin
|
451
506
|
result = ReactOnRails::ServerRenderingPool.server_render_js_with_console_logging(js_code, render_options)
|
452
507
|
rescue StandardError => err
|
453
508
|
# This error came from the renderer
|
454
|
-
raise ReactOnRails::PrerenderError
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
509
|
+
raise ReactOnRails::PrerenderError.new(component_name: react_component_name,
|
510
|
+
# Sanitize as this might be browser logged
|
511
|
+
props: sanitized_props_string(props),
|
512
|
+
err: err,
|
513
|
+
js_code: js_code)
|
459
514
|
end
|
460
515
|
|
461
516
|
if result["hasErrors"] && render_options.raise_on_prerender_error
|
462
517
|
# We caught this exception on our backtrace handler
|
463
|
-
raise ReactOnRails::PrerenderError
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
518
|
+
raise ReactOnRails::PrerenderError.new(component_name: react_component_name,
|
519
|
+
# Sanitize as this might be browser logged
|
520
|
+
props: sanitized_props_string(props),
|
521
|
+
err: nil,
|
522
|
+
js_code: js_code,
|
523
|
+
console_messages: result["consoleReplayScript"])
|
469
524
|
|
470
525
|
end
|
471
526
|
result
|
472
527
|
end
|
473
528
|
|
474
529
|
def initialize_redux_stores
|
475
|
-
|
476
|
-
declarations = "var reduxProps, store, storeGenerator;\n".dup
|
477
|
-
all_stores = (@registered_stores || []) + (@registered_stores_defer_render || [])
|
478
|
-
|
479
|
-
result = <<-JS.dup
|
530
|
+
result = +<<-JS
|
480
531
|
ReactOnRails.clearHydratedStores();
|
481
532
|
JS
|
482
533
|
|
534
|
+
return result unless @registered_stores.present? || @registered_stores_defer_render.present?
|
535
|
+
|
536
|
+
declarations = +"var reduxProps, store, storeGenerator;\n"
|
537
|
+
all_stores = (@registered_stores || []) + (@registered_stores_defer_render || [])
|
538
|
+
|
483
539
|
result << all_stores.each_with_object(declarations) do |redux_store_data, memo|
|
484
540
|
store_name = redux_store_data[:store_name]
|
485
541
|
props = props_string(redux_store_data[:props])
|
@@ -493,79 +549,34 @@ module ReactOnRails
|
|
493
549
|
result
|
494
550
|
end
|
495
551
|
|
496
|
-
# This is the definitive list of the default values used for the rails_context, which is the
|
497
|
-
# second parameter passed to both component and store generator functions.
|
498
|
-
# rubocop:disable Metrics/AbcSize
|
499
|
-
def rails_context(server_side: required("server_side"))
|
500
|
-
@rails_context ||= begin
|
501
|
-
result = {
|
502
|
-
railsEnv: Rails.env,
|
503
|
-
inMailer: in_mailer?,
|
504
|
-
# Locale settings
|
505
|
-
i18nLocale: I18n.locale,
|
506
|
-
i18nDefaultLocale: I18n.default_locale,
|
507
|
-
rorVersion: ReactOnRails::VERSION,
|
508
|
-
rorPro: ReactOnRails::Utils.react_on_rails_pro?
|
509
|
-
}
|
510
|
-
if defined?(request) && request.present?
|
511
|
-
# Check for encoding of the request's original_url and try to force-encoding the
|
512
|
-
# URLs as UTF-8. This situation can occur in browsers that do not encode the
|
513
|
-
# entire URL as UTF-8 already, mostly on the Windows platform (IE11 and lower).
|
514
|
-
original_url_normalized = request.original_url
|
515
|
-
if original_url_normalized.encoding.to_s == "ASCII-8BIT"
|
516
|
-
original_url_normalized = original_url_normalized.force_encoding("ISO-8859-1").encode("UTF-8")
|
517
|
-
end
|
518
|
-
|
519
|
-
# Using Addressable instead of standard URI to better deal with
|
520
|
-
# non-ASCII characters (see https://github.com/shakacode/react_on_rails/pull/405)
|
521
|
-
uri = Addressable::URI.parse(original_url_normalized)
|
522
|
-
# uri = Addressable::URI.parse("http://foo.com:3000/posts?id=30&limit=5#time=1305298413")
|
523
|
-
|
524
|
-
result.merge!(
|
525
|
-
# URL settings
|
526
|
-
href: uri.to_s,
|
527
|
-
location: "#{uri.path}#{uri.query.present? ? "?#{uri.query}" : ''}",
|
528
|
-
scheme: uri.scheme, # http
|
529
|
-
host: uri.host, # foo.com
|
530
|
-
port: uri.port,
|
531
|
-
pathname: uri.path, # /posts
|
532
|
-
search: uri.query, # id=30&limit=5
|
533
|
-
httpAcceptLanguage: request.env["HTTP_ACCEPT_LANGUAGE"]
|
534
|
-
)
|
535
|
-
end
|
536
|
-
if ReactOnRails.configuration.rendering_extension
|
537
|
-
custom_context = ReactOnRails.configuration.rendering_extension.custom_context(self)
|
538
|
-
result.merge!(custom_context) if custom_context
|
539
|
-
end
|
540
|
-
result
|
541
|
-
end
|
542
|
-
|
543
|
-
@rails_context.merge(serverSide: server_side)
|
544
|
-
end
|
545
|
-
|
546
|
-
# rubocop:enable Metrics/AbcSize
|
547
|
-
|
548
552
|
def replay_console_option(val)
|
549
553
|
val.nil? ? ReactOnRails.configuration.replay_console : val
|
550
554
|
end
|
551
555
|
|
552
|
-
def use_hot_reloading?
|
553
|
-
ENV["REACT_ON_RAILS_ENV"] == "HOT"
|
554
|
-
end
|
555
|
-
|
556
|
-
def send_tag_method(tag_method_name, args)
|
557
|
-
asset_type = use_hot_reloading? ? :hot : :static
|
558
|
-
assets = Array(args[asset_type])
|
559
|
-
options = args.delete_if { |key, _value| %i[hot static].include?(key) }
|
560
|
-
send(tag_method_name, *assets, options) unless assets.empty?
|
561
|
-
end
|
562
|
-
|
563
556
|
def in_mailer?
|
564
557
|
return false unless defined?(controller)
|
565
558
|
return false unless defined?(ActionMailer::Base)
|
566
559
|
|
567
560
|
controller.is_a?(ActionMailer::Base)
|
568
561
|
end
|
562
|
+
|
563
|
+
if defined?(ScoutApm)
|
564
|
+
include ScoutApm::Tracer
|
565
|
+
instrument_method :react_component, type: "ReactOnRails", name: "react_component"
|
566
|
+
instrument_method :react_component_hash, type: "ReactOnRails", name: "react_component_hash"
|
567
|
+
end
|
568
|
+
|
569
|
+
def raise_missing_autoloaded_bundle(react_component_name)
|
570
|
+
msg = <<~MSG
|
571
|
+
**ERROR** ReactOnRails: Component "#{react_component_name}" is configured as "auto_load_bundle: true"
|
572
|
+
but the generated component entrypoint, which should have been at #{generated_components_pack_path(react_component_name)},
|
573
|
+
is missing. You might want to check that this component is in a directory named "#{ReactOnRails.configuration.components_subdirectory}"
|
574
|
+
& that "bundle exec rake react_on_rails:generate_packs" has been run.
|
575
|
+
MSG
|
576
|
+
|
577
|
+
raise ReactOnRails::Error, msg
|
578
|
+
end
|
569
579
|
end
|
570
580
|
end
|
571
581
|
# rubocop:enable Metrics/ModuleLength
|
582
|
+
# rubocop:enable Metrics/MethodLength
|
@@ -11,7 +11,7 @@ module ReactOnRails
|
|
11
11
|
"\u2028" => '\u2028',
|
12
12
|
"\u2029" => '\u2029'
|
13
13
|
}.freeze
|
14
|
-
ESCAPE_REGEXP = /[\u2028\u2029&><]/u
|
14
|
+
ESCAPE_REGEXP = /[\u2028\u2029&><]/u.freeze
|
15
15
|
|
16
16
|
def self.escape(json)
|
17
17
|
return escape_without_erb_util(json) if Utils.rails_version_less_than_4_1_1
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ReactOnRails
|
4
|
+
class JsonParseError < ::ReactOnRails::Error
|
5
|
+
attr_reader :json
|
6
|
+
|
7
|
+
def initialize(parse_error:, json:)
|
8
|
+
@json = json
|
9
|
+
@original_error = parse_error
|
10
|
+
super(parse_error.message)
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_honeybadger_context
|
14
|
+
to_error_context
|
15
|
+
end
|
16
|
+
|
17
|
+
def raven_context
|
18
|
+
to_error_context
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_error_context
|
22
|
+
{
|
23
|
+
original_error: @original_error,
|
24
|
+
json: @json
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|