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.
Files changed (199) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +338 -0
  3. data/.eslintignore +2 -1
  4. data/.eslintrc +32 -3
  5. data/.github/FUNDING.yml +1 -0
  6. data/.github/ISSUE_TEMPLATE/bug_report.md +23 -0
  7. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  8. data/.github/PULL_REQUEST_TEMPLATE.md +19 -0
  9. data/.github/workflows/lint-js-and-ruby.yml +54 -0
  10. data/.github/workflows/main.yml +183 -0
  11. data/.github/workflows/package-js-tests.yml +35 -0
  12. data/.github/workflows/rspec-package-specs.yml +46 -0
  13. data/.gitignore +3 -4
  14. data/.prettierignore +14 -0
  15. data/.prettierrc +20 -0
  16. data/.rubocop.yml +76 -34
  17. data/.travis.yml +15 -22
  18. data/CHANGELOG.md +443 -55
  19. data/CONTRIBUTING.md +62 -80
  20. data/Gemfile +1 -35
  21. data/Gemfile.development_dependencies +50 -0
  22. data/KUDOS.md +4 -1
  23. data/{docs/LICENSE.md → LICENSE.md} +1 -1
  24. data/NEWS.md +14 -4
  25. data/REACT-ON-RAILS-PRO-LICENSE +95 -0
  26. data/README.md +107 -802
  27. data/Rakefile +1 -8
  28. data/SUMMARY.md +51 -29
  29. data/book.json +5 -5
  30. data/docs/{basics/generator.md → additional-details/generator-details.md} +5 -13
  31. data/docs/{basics/installation-overview.md → additional-details/manual-installation-overview.md} +9 -14
  32. data/docs/{basics → additional-details}/migrating-from-react-rails.md +1 -1
  33. data/docs/additional-details/recommended-project-structure.md +69 -0
  34. data/docs/additional-details/tips-for-usage-with-sp6.md +15 -0
  35. data/docs/additional-details/upgrade-webpacker-v3-to-v4.md +10 -0
  36. data/docs/api/javascript-api.md +35 -6
  37. data/docs/api/redux-store-api.md +102 -0
  38. data/docs/api/view-helpers-api.md +133 -0
  39. data/docs/contributor-info/errors-with-hooks.md +45 -0
  40. data/docs/contributor-info/linters.md +5 -6
  41. data/docs/contributor-info/pull-requests.md +42 -0
  42. data/docs/contributor-info/releasing.md +1 -1
  43. data/docs/deployment/heroku-deployment.md +39 -0
  44. data/docs/getting-started.md +196 -0
  45. data/docs/guides/client-vs-server-rendering.md +27 -0
  46. data/docs/guides/configuration.md +289 -0
  47. data/docs/guides/deployment.md +5 -0
  48. data/docs/guides/file-system-based-automated-bundle-generation.md +197 -0
  49. data/docs/guides/hmr-and-hot-reloading-with-the-webpack-dev-server.md +104 -0
  50. data/docs/guides/how-react-on-rails-works.md +44 -0
  51. data/docs/guides/how-to-conditionally-server-render-based-on-device-type.md +40 -0
  52. data/docs/guides/how-to-use-different-files-for-client-and-server-rendering.md +98 -0
  53. data/docs/guides/i18n.md +87 -0
  54. data/docs/guides/installation-into-an-existing-rails-app.md +66 -0
  55. data/docs/guides/minitest-configuration.md +31 -0
  56. data/docs/guides/rails-webpacker-react-integration-options.md +213 -0
  57. data/docs/guides/react-on-rails-overview.md +29 -0
  58. data/docs/guides/react-server-rendering.md +32 -0
  59. data/docs/guides/render-functions-and-railscontext.md +205 -0
  60. data/docs/guides/rspec-configuration.md +73 -0
  61. data/docs/guides/tutorial.md +371 -0
  62. data/docs/{basics → guides}/upgrading-react-on-rails.md +126 -3
  63. data/docs/guides/webpack-configuration.md +42 -0
  64. data/docs/home.md +23 -0
  65. data/docs/javascript/asset-pipeline.md +12 -0
  66. data/docs/{additional-reading → javascript}/code-splitting.md +21 -11
  67. data/docs/javascript/converting-from-custom-webpack-config-to-rails-webpacker-config.md +10 -0
  68. data/docs/javascript/credits.md +10 -0
  69. data/docs/{additional-reading → javascript}/images.md +5 -6
  70. data/docs/javascript/react-helmet.md +100 -0
  71. data/docs/javascript/react-router.md +90 -0
  72. data/docs/{additional-reading → javascript}/server-rendering-tips.md +15 -12
  73. data/docs/javascript/troubleshooting-when-using-shakapacker.md +77 -0
  74. data/docs/{additional-reading → javascript}/webpack.md +2 -2
  75. data/docs/misc/articles.md +20 -0
  76. data/docs/misc/doctrine.md +5 -6
  77. data/docs/outdated/deferred-rendering.md +39 -0
  78. data/docs/{additional-reading → outdated}/rails-assets-relative-paths.md +4 -4
  79. data/docs/{additional-reading → outdated}/rails-assets.md +12 -20
  80. data/docs/{misc → outdated}/rails3.md +2 -2
  81. data/docs/rails/convert-rails-5-api-only-app.md +19 -0
  82. data/docs/rails/rails-engine-integration.md +32 -0
  83. data/docs/{additional-reading → rails}/rails_view_rendering_from_inline_javascript.md +2 -1
  84. data/docs/{additional-reading → rails}/turbolinks.md +13 -1
  85. data/docs/react-on-rails-pro/react-on-rails-pro.md +43 -0
  86. data/docs/testimonials/hvmn.md +25 -0
  87. data/docs/testimonials/resortpass.md +13 -0
  88. data/docs/testimonials/testimonials.md +28 -0
  89. data/jest.config.js +4 -0
  90. data/lib/generators/USAGE +1 -1
  91. data/lib/generators/react_on_rails/adapt_for_older_shakapacker_generator.rb +41 -0
  92. data/lib/generators/react_on_rails/base_generator.rb +55 -43
  93. data/lib/generators/react_on_rails/bin/dev +30 -0
  94. data/lib/generators/react_on_rails/bin/dev-static +30 -0
  95. data/lib/generators/react_on_rails/dev_tests_generator.rb +4 -3
  96. data/lib/generators/react_on_rails/generator_helper.rb +8 -6
  97. data/lib/generators/react_on_rails/generator_messages.rb +40 -0
  98. data/lib/generators/react_on_rails/install_generator.rb +37 -0
  99. data/lib/generators/react_on_rails/templates/.eslintrc +3 -1
  100. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev +4 -6
  101. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static +9 -0
  102. data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +21 -40
  103. data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +4 -0
  104. data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorldServer.js +5 -0
  105. data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/server-bundle.js +8 -0
  106. data/lib/generators/react_on_rails/templates/base/base/app/views/layouts/hello_world.html.erb +2 -1
  107. data/lib/generators/react_on_rails/templates/base/base/babel.config.js.tt +32 -0
  108. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb +20 -4
  109. data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +62 -0
  110. data/lib/generators/react_on_rails/templates/base/base/config/webpack/clientWebpackConfig.js.tt +17 -0
  111. data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +17 -0
  112. data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +25 -0
  113. data/lib/generators/react_on_rails/templates/base/base/config/webpack/production.js.tt +9 -0
  114. data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +117 -0
  115. data/lib/generators/react_on_rails/templates/base/base/config/webpack/test.js.tt +9 -0
  116. data/lib/generators/react_on_rails/templates/base/base/config/webpack/webpack.config.js.tt +15 -0
  117. data/lib/generators/react_on_rails/templates/base/base/config/webpack/webpackConfig.js.tt +36 -0
  118. data/lib/generators/react_on_rails/templates/dev_tests/spec/rails_helper.rb +8 -2
  119. data/lib/generators/react_on_rails/templates/dev_tests/spec/simplecov_helper.rb +1 -1
  120. data/lib/generators/react_on_rails/templates/dev_tests/spec/{features → system}/hello_world_spec.rb +2 -2
  121. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +6 -9
  122. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.js +1 -3
  123. data/lib/react_on_rails/configuration.rb +198 -145
  124. data/lib/react_on_rails/error.rb +2 -0
  125. data/lib/react_on_rails/git_utils.rb +5 -3
  126. data/lib/react_on_rails/{react_on_rails_helper.rb → helper.rb} +201 -190
  127. data/lib/react_on_rails/json_output.rb +1 -1
  128. data/lib/react_on_rails/json_parse_error.rb +28 -0
  129. data/lib/react_on_rails/locales/base.rb +169 -0
  130. data/lib/react_on_rails/locales/to_js.rb +33 -0
  131. data/lib/react_on_rails/locales/to_json.rb +23 -0
  132. data/lib/react_on_rails/packs_generator.rb +234 -0
  133. data/lib/react_on_rails/prerender_error.rb +35 -27
  134. data/lib/react_on_rails/react_component/render_options.rb +64 -9
  135. data/lib/react_on_rails/server_rendering_js_code.rb +55 -0
  136. data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +126 -76
  137. data/lib/react_on_rails/server_rendering_pool.rb +0 -1
  138. data/lib/react_on_rails/test_helper/ensure_assets_compiled.rb +9 -8
  139. data/lib/react_on_rails/test_helper/webpack_assets_compiler.rb +17 -0
  140. data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +13 -12
  141. data/lib/react_on_rails/test_helper.rb +24 -3
  142. data/lib/react_on_rails/utils.rb +94 -25
  143. data/lib/react_on_rails/version.rb +1 -1
  144. data/lib/react_on_rails/version_checker.rb +5 -1
  145. data/lib/react_on_rails/version_syntax_converter.rb +14 -12
  146. data/lib/react_on_rails/webpacker_utils.rb +105 -5
  147. data/lib/react_on_rails.rb +8 -2
  148. data/lib/tasks/assets.rake +28 -60
  149. data/lib/tasks/generate_packs.rake +11 -0
  150. data/lib/tasks/locale.rake +5 -4
  151. data/package-scripts.yml +49 -0
  152. data/package.json +52 -47
  153. data/rakelib/docker.rake +0 -5
  154. data/rakelib/dummy_apps.rake +5 -8
  155. data/rakelib/example_type.rb +12 -3
  156. data/rakelib/examples.rake +5 -4
  157. data/rakelib/lint.rake +5 -16
  158. data/rakelib/node_package.rake +2 -2
  159. data/rakelib/release.rake +37 -23
  160. data/rakelib/run_rspec.rake +16 -44
  161. data/rakelib/task_helpers.rb +16 -4
  162. data/react_on_rails.gemspec +6 -22
  163. data/tsconfig.json +14 -0
  164. data/webpackConfigLoader.js +5 -4
  165. data/yarn.lock +5935 -3106
  166. metadata +122 -272
  167. data/Gemfile.rails32 +0 -74
  168. data/docs/additional-reading/asset-pipeline.md +0 -20
  169. data/docs/additional-reading/babel.md +0 -5
  170. data/docs/additional-reading/caching-and-performance.md +0 -4
  171. data/docs/additional-reading/heroku-deployment.md +0 -92
  172. data/docs/additional-reading/hot-reloading-rails-development.md +0 -57
  173. data/docs/additional-reading/node-server-rendering.md +0 -5
  174. data/docs/additional-reading/rails-engine-integration.md +0 -34
  175. data/docs/additional-reading/react-helmet.md +0 -80
  176. data/docs/additional-reading/react-router.md +0 -113
  177. data/docs/additional-reading/recommended-project-structure.md +0 -49
  178. data/docs/additional-reading/rspec-configuration.md +0 -56
  179. data/docs/additional-reading/webpack-dev-server.md +0 -15
  180. data/docs/api/ruby-api-hot-reload-view-helpers.md +0 -44
  181. data/docs/api/ruby-api.md +0 -8
  182. data/docs/basics/configuration.md +0 -163
  183. data/docs/basics/i18n.md +0 -77
  184. data/docs/tutorial.md +0 -220
  185. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-server +0 -12
  186. data/lib/react_on_rails/assets_precompile.rb +0 -150
  187. data/lib/react_on_rails/locales_to_js.rb +0 -134
  188. data/ruby-lint.yml +0 -25
  189. /data/docs/{additional-reading → additional-details}/updating-dependencies.md +0 -0
  190. /data/docs/{additional-reading → deployment}/elastic-beanstalk.md +0 -0
  191. /data/docs/{additional-reading → javascript}/angular-js-integration-migration.md +0 -0
  192. /data/docs/{additional-reading → javascript}/capistrano-deployment.md +0 -0
  193. /data/docs/{additional-reading → javascript}/foreman-issues.md +0 -0
  194. /data/docs/{additional-reading → javascript}/node-dependencies-and-npm.md +0 -0
  195. /data/docs/{additional-reading → javascript}/react-and-redux.md +0 -0
  196. /data/docs/{additional-reading → javascript}/troubleshooting-when-using-webpacker.md +0 -0
  197. /data/docs/{additional-reading → javascript}/webpack-v1-notes.md +0 -0
  198. /data/docs/{coding-style → misc}/style.md +0 -0
  199. /data/docs/{additional-reading → misc}/tips.md +0 -0
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module ReactOnRails
6
+ module Locales
7
+ def self.compile
8
+ config = ReactOnRails.configuration
9
+ check_config_directory_exists(
10
+ directory: config.i18n_dir, key_name: "config.i18n_dir",
11
+ remove_if: "not using the React on Rails i18n feature"
12
+ )
13
+ check_config_directory_exists(
14
+ directory: config.i18n_yml_dir, key_name: "config.i18n_yml_dir",
15
+ remove_if: "not using this i18n with React on Rails, or if you want to use all translation files"
16
+ )
17
+ if config.i18n_output_format&.downcase == "js"
18
+ ReactOnRails::Locales::ToJs.new
19
+ else
20
+ ReactOnRails::Locales::ToJson.new
21
+ end
22
+ end
23
+
24
+ def self.check_config_directory_exists(directory:, key_name:, remove_if:)
25
+ return if directory.nil?
26
+ return if Dir.exist?(directory)
27
+
28
+ msg = <<~MSG
29
+ Error configuring /config/initializers/react_on_rails.rb: invalid value for `#{key_name}`.
30
+ Directory does not exist: #{directory}. Set to value to nil or comment it
31
+ out if #{remove_if}.
32
+ MSG
33
+ raise ReactOnRails::Error, msg
34
+ end
35
+
36
+ private_class_method :check_config_directory_exists
37
+
38
+ class Base
39
+ def initialize
40
+ return if i18n_dir.nil?
41
+ return unless obsolete?
42
+
43
+ @translations, @defaults = generate_translations
44
+ convert
45
+ end
46
+
47
+ private
48
+
49
+ def file_format; end
50
+
51
+ def obsolete?
52
+ return true if exist_files.empty?
53
+
54
+ files_are_outdated
55
+ end
56
+
57
+ def exist_files
58
+ @exist_files ||= files.select { |file| File.exist?(file) }
59
+ end
60
+
61
+ def files_are_outdated
62
+ latest_yml = locale_files.map { |file| File.mtime(file) }.max
63
+ earliest = exist_files.map { |file| File.mtime(file) }.min
64
+ latest_yml > earliest
65
+ end
66
+
67
+ def file_names
68
+ %w[translations default]
69
+ end
70
+
71
+ def files
72
+ @files ||= file_names.map { |n| file(n) }
73
+ end
74
+
75
+ def file(name)
76
+ "#{i18n_dir}/#{name}.#{file_format}"
77
+ end
78
+
79
+ def locale_files
80
+ @locale_files ||= if i18n_yml_dir.present?
81
+ Dir["#{i18n_yml_dir}/**/*.yml"]
82
+ else
83
+ ReactOnRails::Utils.truthy_presence(
84
+ Rails.application && Rails.application.config.i18n.load_path
85
+ ).presence
86
+ end
87
+ end
88
+
89
+ def i18n_dir
90
+ @i18n_dir ||= ReactOnRails.configuration.i18n_dir
91
+ end
92
+
93
+ def i18n_yml_dir
94
+ @i18n_yml_dir ||= ReactOnRails.configuration.i18n_yml_dir
95
+ end
96
+
97
+ def default_locale
98
+ @default_locale ||= I18n.default_locale.to_s || "en"
99
+ end
100
+
101
+ def convert
102
+ file_names.each do |name|
103
+ template = send("template_#{name}")
104
+ path = file(name)
105
+ generate_file(template, path)
106
+ end
107
+ end
108
+
109
+ def generate_file(template, path)
110
+ result = ERB.new(template).result()
111
+ File.write(path, result)
112
+ end
113
+
114
+ def generate_translations
115
+ translations = {}
116
+ defaults = {}
117
+ locale_files.each do |f|
118
+ translation = YAML.safe_load(File.open(f))
119
+ key = translation.keys[0]
120
+ val = flatten(translation[key])
121
+ translations = translations.deep_merge(key => val)
122
+ defaults = defaults.deep_merge(flatten_defaults(val)) if key == default_locale
123
+ end
124
+ [translations.to_json, defaults.to_json]
125
+ end
126
+
127
+ def format(input)
128
+ input.to_s.tr(".", "_").camelize(:lower).to_sym
129
+ end
130
+
131
+ def flatten_defaults(val)
132
+ flatten(val).each_with_object({}) do |(k, v), h|
133
+ key = format(k)
134
+ h[key] = { id: k, defaultMessage: v }
135
+ end
136
+ end
137
+
138
+ def flatten(translations)
139
+ translations.each_with_object({}) do |(k, v), h|
140
+ if v.is_a? Hash
141
+ flatten(v).map { |hk, hv| h["#{k}.#{hk}".to_sym] = hv }
142
+ elsif v.is_a?(String)
143
+ h[k] = v.gsub("%{", "{")
144
+ elsif !v.is_a?(Array)
145
+ h[k] = v
146
+ end
147
+ end
148
+ end
149
+
150
+ def template_translations
151
+ <<-JS.strip_heredoc
152
+ export const translations = #{@translations};
153
+ JS
154
+ end
155
+
156
+ def template_default
157
+ <<-JS.strip_heredoc
158
+ import { defineMessages } from 'react-intl';
159
+
160
+ const defaultLocale = \'#{default_locale}\';
161
+
162
+ const defaultMessages = defineMessages(#{@defaults});
163
+
164
+ export { defaultMessages, defaultLocale };
165
+ JS
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module ReactOnRails
6
+ module Locales
7
+ class ToJs < Base
8
+ private
9
+
10
+ def file_format
11
+ "js"
12
+ end
13
+
14
+ def template_translations
15
+ <<-JS.strip_heredoc
16
+ export const translations = #{@translations};
17
+ JS
18
+ end
19
+
20
+ def template_default
21
+ <<-JS.strip_heredoc
22
+ import { defineMessages } from 'react-intl';
23
+
24
+ const defaultLocale = \'#{default_locale}\';
25
+
26
+ const defaultMessages = defineMessages(#{@defaults});
27
+
28
+ export { defaultMessages, defaultLocale };
29
+ JS
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module ReactOnRails
6
+ module Locales
7
+ class ToJson < Base
8
+ private
9
+
10
+ def file_format
11
+ "json"
12
+ end
13
+
14
+ def template_translations
15
+ @translations
16
+ end
17
+
18
+ def template_default
19
+ @defaults
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ module ReactOnRails
6
+ # rubocop:disable Metrics/ClassLength
7
+ class PacksGenerator
8
+ CONTAINS_CLIENT_OR_SERVER_REGEX = /\.(server|client)($|\.)/.freeze
9
+ MINIMUM_SHAKAPACKER_VERSION = [6, 5, 1].freeze
10
+
11
+ def self.instance
12
+ @instance ||= PacksGenerator.new
13
+ end
14
+
15
+ def generate_packs_if_stale
16
+ return unless ReactOnRails.configuration.auto_load_bundle
17
+
18
+ are_generated_files_present_and_up_to_date = Dir.exist?(generated_packs_directory_path) &&
19
+ File.exist?(generated_server_bundle_file_path) &&
20
+ !stale_or_missing_packs?
21
+
22
+ return if are_generated_files_present_and_up_to_date
23
+
24
+ clean_generated_packs_directory
25
+ generate_packs
26
+ end
27
+
28
+ private
29
+
30
+ def generate_packs
31
+ common_component_to_path.each_value { |component_path| create_pack(component_path) }
32
+ client_component_to_path.each_value { |component_path| create_pack(component_path) }
33
+
34
+ create_server_pack if ReactOnRails.configuration.server_bundle_js_file.present?
35
+ end
36
+
37
+ def create_pack(file_path)
38
+ output_path = generated_pack_path(file_path)
39
+ content = pack_file_contents(file_path)
40
+
41
+ File.write(output_path, content)
42
+
43
+ puts(Rainbow("Generated Packs: #{output_path}").yellow)
44
+ end
45
+
46
+ def pack_file_contents(file_path)
47
+ registered_component_name = component_name(file_path)
48
+ <<~FILE_CONTENT
49
+ import ReactOnRails from 'react-on-rails';
50
+ import #{registered_component_name} from '#{relative_component_path_from_generated_pack(file_path)}';
51
+
52
+ ReactOnRails.register({#{registered_component_name}});
53
+ FILE_CONTENT
54
+ end
55
+
56
+ def create_server_pack
57
+ File.write(generated_server_bundle_file_path, generated_server_pack_file_content)
58
+
59
+ add_generated_pack_to_server_bundle
60
+ puts(Rainbow("Generated Server Bundle: #{generated_server_bundle_file_path}").orange)
61
+ end
62
+
63
+ def generated_server_pack_file_content
64
+ common_components_for_server_bundle = common_component_to_path.delete_if { |k| server_component_to_path.key?(k) }
65
+ component_for_server_registration_to_path = common_components_for_server_bundle.merge(server_component_to_path)
66
+
67
+ server_component_imports = component_for_server_registration_to_path.map do |name, component_path|
68
+ "import #{name} from '#{relative_path(generated_server_bundle_file_path, component_path)}';"
69
+ end
70
+
71
+ components_to_register = component_for_server_registration_to_path.keys
72
+
73
+ <<~FILE_CONTENT
74
+ import ReactOnRails from 'react-on-rails';
75
+
76
+ #{server_component_imports.join("\n")}
77
+
78
+ ReactOnRails.register({#{components_to_register.join(",\n")}});
79
+ FILE_CONTENT
80
+ end
81
+
82
+ def add_generated_pack_to_server_bundle
83
+ return if ReactOnRails.configuration.make_generated_server_bundle_the_entrypoint
84
+
85
+ relative_path_to_generated_server_bundle = relative_path(server_bundle_entrypoint,
86
+ generated_server_bundle_file_path)
87
+ content = <<~FILE_CONTENT
88
+ // import statement added by react_on_rails:generate_packs rake task
89
+ import "./#{relative_path_to_generated_server_bundle}"
90
+ FILE_CONTENT
91
+
92
+ ReactOnRails::Utils.prepend_to_file_if_text_not_present(
93
+ file: server_bundle_entrypoint,
94
+ text_to_prepend: content,
95
+ regex: %r{import ['"]\./#{relative_path_to_generated_server_bundle}['"]}
96
+ )
97
+ end
98
+
99
+ def generated_server_bundle_file_path
100
+ return server_bundle_entrypoint if ReactOnRails.configuration.make_generated_server_bundle_the_entrypoint
101
+
102
+ generated_server_bundle_file_path = server_bundle_entrypoint.sub(".js", "-generated.js")
103
+ generated_server_bundle_file_name = component_name(generated_server_bundle_file_path)
104
+ source_entrypoint_parent = Pathname(ReactOnRails::WebpackerUtils.webpacker_source_entry_path).parent
105
+ generated_nonentrypoints_path = "#{source_entrypoint_parent}/generated"
106
+
107
+ FileUtils.mkdir_p(generated_nonentrypoints_path)
108
+ "#{generated_nonentrypoints_path}/#{generated_server_bundle_file_name}.js"
109
+ end
110
+
111
+ def clean_generated_packs_directory
112
+ FileUtils.rm_rf(generated_packs_directory_path)
113
+ FileUtils.mkdir_p(generated_packs_directory_path)
114
+ end
115
+
116
+ def server_bundle_entrypoint
117
+ Rails.root.join(ReactOnRails::WebpackerUtils.webpacker_source_entry_path,
118
+ ReactOnRails.configuration.server_bundle_js_file)
119
+ end
120
+
121
+ def generated_packs_directory_path
122
+ source_entry_path = ReactOnRails::WebpackerUtils.webpacker_source_entry_path
123
+
124
+ "#{source_entry_path}/generated"
125
+ end
126
+
127
+ def relative_component_path_from_generated_pack(ror_component_path)
128
+ component_file_pathname = Pathname.new(ror_component_path)
129
+ component_generated_pack_path = generated_pack_path(ror_component_path)
130
+ generated_pack_pathname = Pathname.new(component_generated_pack_path)
131
+
132
+ relative_path(generated_pack_pathname, component_file_pathname)
133
+ end
134
+
135
+ def relative_path(from, to)
136
+ from_path = Pathname.new(from)
137
+ to_path = Pathname.new(to)
138
+
139
+ relative_path = to_path.relative_path_from(from_path)
140
+ relative_path.sub("../", "")
141
+ end
142
+
143
+ def generated_pack_path(file_path)
144
+ "#{generated_packs_directory_path}/#{component_name(file_path)}.js"
145
+ end
146
+
147
+ def component_name(file_path)
148
+ basename = File.basename(file_path, File.extname(file_path))
149
+
150
+ basename.sub(CONTAINS_CLIENT_OR_SERVER_REGEX, "")
151
+ end
152
+
153
+ def component_name_to_path(paths)
154
+ paths.to_h { |path| [component_name(path), path] }
155
+ end
156
+
157
+ def common_component_to_path
158
+ common_components_paths = Dir.glob("#{components_search_path}/*").reject do |f|
159
+ CONTAINS_CLIENT_OR_SERVER_REGEX.match?(f)
160
+ end
161
+ component_name_to_path(common_components_paths)
162
+ end
163
+
164
+ def client_component_to_path
165
+ client_render_components_paths = Dir.glob("#{components_search_path}/*.client.*")
166
+ client_specific_components = component_name_to_path(client_render_components_paths)
167
+
168
+ duplicate_components = common_component_to_path.slice(*client_specific_components.keys)
169
+ duplicate_components.each_key { |component| raise_client_component_overrides_common(component) }
170
+
171
+ client_specific_components
172
+ end
173
+
174
+ def server_component_to_path
175
+ server_render_components_paths = Dir.glob("#{components_search_path}/*.server.*")
176
+ server_specific_components = component_name_to_path(server_render_components_paths)
177
+
178
+ duplicate_components = common_component_to_path.slice(*server_specific_components.keys)
179
+ duplicate_components.each_key { |component| raise_server_component_overrides_common(component) }
180
+
181
+ server_specific_components.each_key do |k|
182
+ raise_missing_client_component(k) unless client_component_to_path.key?(k)
183
+ end
184
+
185
+ server_specific_components
186
+ end
187
+
188
+ def components_search_path
189
+ source_path = ReactOnRails::WebpackerUtils.webpacker_source_path
190
+
191
+ "#{source_path}/**/#{ReactOnRails.configuration.components_subdirectory}"
192
+ end
193
+
194
+ def raise_client_component_overrides_common(component_name)
195
+ msg = <<~MSG
196
+ **ERROR** ReactOnRails: client specific definition for Component '#{component_name}' overrides the \
197
+ common definition. Please delete the common definition and have separate server and client files. For more \
198
+ information, please see https://www.shakacode.com/react-on-rails/docs/guides/file-system-based-automated-bundle-generation.md
199
+ MSG
200
+
201
+ raise ReactOnRails::Error, msg
202
+ end
203
+
204
+ def raise_server_component_overrides_common(component_name)
205
+ msg = <<~MSG
206
+ **ERROR** ReactOnRails: server specific definition for Component '#{component_name}' overrides the \
207
+ common definition. Please delete the common definition and have separate server and client files. For more \
208
+ information, please see https://www.shakacode.com/react-on-rails/docs/guides/file-system-based-automated-bundle-generation.md
209
+ MSG
210
+
211
+ raise ReactOnRails::Error, msg
212
+ end
213
+
214
+ def raise_missing_client_component(component_name)
215
+ msg = <<~MSG
216
+ **ERROR** ReactOnRails: Component '#{component_name}' is missing a client specific file. For more \
217
+ information, please see https://www.shakacode.com/react-on-rails/docs/guides/file-system-based-automated-bundle-generation.md
218
+ MSG
219
+
220
+ raise ReactOnRails::Error, msg
221
+ end
222
+
223
+ def stale_or_missing_packs?
224
+ component_files = common_component_to_path.values + client_component_to_path.values
225
+ most_recent_mtime = Utils.find_most_recent_mtime(component_files).to_i
226
+
227
+ component_files.each_with_object([]).any? do |file|
228
+ path = generated_pack_path(file)
229
+ !File.exist?(path) || File.mtime(path).to_i < most_recent_mtime
230
+ end
231
+ end
232
+ end
233
+ # rubocop:enable Metrics/ClassLength
234
+ end
@@ -3,6 +3,9 @@
3
3
  # rubocop:disable: Layout/IndentHeredoc
4
4
  module ReactOnRails
5
5
  class PrerenderError < ::ReactOnRails::Error
6
+ MAX_ERROR_SNIPPET_TO_LOG = 1000
7
+ # TODO: Consider remove providing original `err` as already have access to `self.cause`
8
+ # http://blog.honeybadger.io/nested-errors-in-ruby-with-exception-cause/
6
9
  attr_reader :component_name, :err, :props, :js_code, :console_messages
7
10
 
8
11
  # err might be nil if JS caught the error
@@ -27,47 +30,52 @@ module ReactOnRails
27
30
  to_error_context
28
31
  end
29
32
 
33
+ def to_error_context
34
+ result = {
35
+ component_name: component_name,
36
+ err: err,
37
+ props: props,
38
+ js_code: js_code,
39
+ console_messages: console_messages
40
+ }
41
+
42
+ result.merge!(err.to_error_context) if err.respond_to?(:to_error_context)
43
+ result
44
+ end
45
+
30
46
  private
31
47
 
32
48
  def calc_message(component_name, console_messages, err, js_code, props)
33
- message = "ERROR in SERVER PRERENDERING\n".dup
49
+ message = +"ERROR in SERVER PRERENDERING\n"
34
50
  if err
35
- # rubocop:disable Layout/IndentHeredoc
36
- message << <<-MSG
37
- Encountered error: \"#{err}\"
51
+ message << <<~MSG
52
+ Encountered error:
53
+
54
+ #{err}
55
+
38
56
  MSG
39
- # rubocop:enable Layout/IndentHeredoc
40
- backtrace = err.backtrace.join("\n")
57
+
58
+ backtrace = err.backtrace.first(15).join("\n")
41
59
  else
42
60
  backtrace = nil
43
61
  end
44
- # rubocop:disable Layout/IndentHeredoc
45
- message << <<-MSG
46
- when prerendering #{component_name} with props: #{props}
47
- js_code was:
48
- #{js_code}
62
+ message << <<~MSG
63
+ when prerendering #{component_name} with props: #{Utils.smart_trim(props, MAX_ERROR_SNIPPET_TO_LOG)}
64
+
65
+ code:
66
+
67
+ #{Utils.smart_trim(js_code, MAX_ERROR_SNIPPET_TO_LOG)}
68
+
49
69
  MSG
50
- # rubocop:enable Layout/IndentHeredoc
51
70
 
52
71
  if console_messages
53
- # rubocop:disable Layout/IndentHeredoc
54
- message << <<-MSG
55
- console messages:
56
- #{console_messages}
72
+ message << <<~MSG
73
+ console messages:
74
+ #{console_messages}
57
75
  MSG
58
- # rubocop:enable Layout/IndentHeredoc
76
+
59
77
  end
60
78
  [backtrace, message]
61
79
  end
62
-
63
- def to_error_context
64
- {
65
- component_name: component_name,
66
- err: err,
67
- props: props,
68
- js_code: js_code,
69
- console_messages: console_messages
70
- }
71
- end
72
80
  end
73
81
  end
@@ -7,8 +7,11 @@ module ReactOnRails
7
7
  class RenderOptions
8
8
  include Utils::Required
9
9
 
10
+ attr_accessor :request_digest
11
+
10
12
  NO_PROPS = {}.freeze
11
13
 
14
+ # TODO: remove the required for named params
12
15
  def initialize(react_component_name: required("react_component_name"), options: required("options"))
13
16
  @react_component_name = react_component_name.camelize
14
17
  @options = options
@@ -16,12 +19,48 @@ module ReactOnRails
16
19
 
17
20
  attr_reader :react_component_name
18
21
 
22
+ def throw_js_errors
23
+ options.fetch(:throw_js_errors, false)
24
+ end
25
+
19
26
  def props
20
27
  options.fetch(:props) { NO_PROPS }
21
28
  end
22
29
 
30
+ def client_props
31
+ props_extension = ReactOnRails.configuration.rendering_props_extension
32
+ if props_extension.present?
33
+ if props_extension.respond_to?(:adjust_props_for_client_side_hydration)
34
+ return props_extension.adjust_props_for_client_side_hydration(react_component_name,
35
+ props.clone)
36
+ end
37
+
38
+ raise ReactOnRails::Error, "ReactOnRails: your rendering_props_extension module is missing the "\
39
+ "required adjust_props_for_client_side_hydration method & can not be used"
40
+ end
41
+ props
42
+ end
43
+
44
+ def random_dom_id
45
+ retrieve_configuration_value_for(:random_dom_id)
46
+ end
47
+
23
48
  def dom_id
24
- @dom_id ||= options.fetch(:id) { generate_unique_dom_id }
49
+ @dom_id ||= options.fetch(:id) do
50
+ if random_dom_id
51
+ generate_unique_dom_id
52
+ else
53
+ base_dom_id
54
+ end
55
+ end
56
+ end
57
+
58
+ def random_dom_id?
59
+ return false if options[:id]
60
+
61
+ return false unless random_dom_id
62
+
63
+ true
25
64
  end
26
65
 
27
66
  def html_options
@@ -29,38 +68,54 @@ module ReactOnRails
29
68
  end
30
69
 
31
70
  def prerender
32
- retrieve_key(:prerender)
71
+ retrieve_configuration_value_for(:prerender)
72
+ end
73
+
74
+ def auto_load_bundle
75
+ retrieve_configuration_value_for(:auto_load_bundle)
33
76
  end
34
77
 
35
78
  def trace
36
- retrieve_key(:trace)
79
+ retrieve_configuration_value_for(:trace)
37
80
  end
38
81
 
39
82
  def replay_console
40
- retrieve_key(:replay_console)
83
+ retrieve_configuration_value_for(:replay_console)
41
84
  end
42
85
 
43
86
  def raise_on_prerender_error
44
- retrieve_key(:raise_on_prerender_error)
87
+ retrieve_configuration_value_for(:raise_on_prerender_error)
45
88
  end
46
89
 
47
90
  def logging_on_server
48
- retrieve_key(:logging_on_server)
91
+ retrieve_configuration_value_for(:logging_on_server)
49
92
  end
50
93
 
51
94
  def to_s
52
- "{ react_component_name = #{react_component_name}, options = #{options}"
95
+ "{ react_component_name = #{react_component_name}, options = #{options}, request_digest = #{request_digest}"
96
+ end
97
+
98
+ def internal_option(key)
99
+ options[key]
100
+ end
101
+
102
+ def set_option(key, value)
103
+ options[key] = value
53
104
  end
54
105
 
55
106
  private
56
107
 
57
108
  attr_reader :options
58
109
 
110
+ def base_dom_id
111
+ "#{react_component_name}-react-component"
112
+ end
113
+
59
114
  def generate_unique_dom_id
60
- "#{react_component_name}-react-component-#{SecureRandom.uuid}"
115
+ "#{base_dom_id}-#{SecureRandom.uuid}"
61
116
  end
62
117
 
63
- def retrieve_key(key)
118
+ def retrieve_configuration_value_for(key)
64
119
  options.fetch(key) do
65
120
  ReactOnRails.configuration.public_send(key)
66
121
  end