react_on_rails 11.2.1 → 12.0.0.pre.beta.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +320 -0
  3. data/.eslintignore +2 -1
  4. data/.eslintrc +23 -1
  5. data/.github/FUNDING.yml +1 -0
  6. data/.gitignore +3 -1
  7. data/.prettierignore +10 -1
  8. data/.prettierrc +3 -0
  9. data/.rubocop.yml +37 -11
  10. data/.travis.yml +10 -20
  11. data/CHANGELOG.md +85 -9
  12. data/CONTRIBUTING.md +60 -71
  13. data/Gemfile +3 -4
  14. data/{COMM-LICENSE → REACT-ON-RAILS-PRO-LICENSE} +6 -9
  15. data/README.md +121 -71
  16. data/Rakefile +0 -7
  17. data/SUMMARY.md +9 -12
  18. data/book.json +5 -5
  19. data/docs/additional-reading/asset-pipeline.md +8 -16
  20. data/docs/additional-reading/react-helmet.md +30 -10
  21. data/docs/additional-reading/react-router.md +52 -75
  22. data/docs/additional-reading/server-rendering-tips.md +12 -7
  23. data/docs/api/javascript-api.md +3 -3
  24. data/docs/api/redux-store-api.md +4 -2
  25. data/docs/api/view-helpers-api.md +8 -9
  26. data/docs/basics/configuration.md +68 -59
  27. data/docs/basics/deployment.md +1 -2
  28. data/docs/basics/hmr-and-hot-reloading-with-the-webpack-dev-server.md +49 -0
  29. data/docs/basics/i18n.md +44 -22
  30. data/docs/basics/installation-into-an-existing-rails-app.md +2 -2
  31. data/docs/basics/minitest-configuration.md +31 -0
  32. data/docs/basics/react-server-rendering.md +1 -1
  33. data/docs/basics/{generator-functions-and-railscontext.md → render-functions-and-railscontext.md} +59 -21
  34. data/docs/basics/rspec-configuration.md +29 -17
  35. data/docs/basics/upgrading-react-on-rails.md +67 -3
  36. data/docs/basics/webpack-configuration.md +15 -1
  37. data/docs/contributor-info/errors-with-hooks.md +45 -0
  38. data/docs/contributor-info/pull-requests.md +44 -0
  39. data/docs/misc/doctrine.md +1 -1
  40. data/docs/{misc-pending → outdated}/code-splitting.md +12 -8
  41. data/docs/{additional-reading → outdated}/heroku-deployment.md +0 -6
  42. data/docs/{basics → outdated}/how-react-on-rails-works.md +3 -3
  43. data/docs/{misc-pending → outdated}/manual-installation-overview.md +5 -5
  44. data/docs/{additional-reading → outdated}/rails-assets-relative-paths.md +3 -3
  45. data/docs/{misc-pending → outdated}/rails-assets.md +4 -7
  46. data/docs/{misc → outdated}/rails3.md +0 -0
  47. data/docs/testimonials/resortpass.md +13 -0
  48. data/docs/testimonials/testimonials.md +11 -1
  49. data/docs/tutorial.md +96 -70
  50. data/jest.config.js +4 -0
  51. data/lib/generators/react_on_rails/base_generator.rb +2 -2
  52. data/lib/generators/react_on_rails/dev_tests_generator.rb +1 -1
  53. data/lib/generators/react_on_rails/generator_helper.rb +4 -6
  54. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev +3 -1
  55. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-hmr +26 -0
  56. data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +20 -40
  57. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb +2 -1
  58. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +4 -8
  59. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.js +1 -3
  60. data/lib/react_on_rails.rb +3 -1
  61. data/lib/react_on_rails/configuration.rb +13 -22
  62. data/lib/react_on_rails/error.rb +2 -0
  63. data/lib/react_on_rails/helper.rb +100 -143
  64. data/lib/react_on_rails/json_parse_error.rb +2 -0
  65. data/lib/react_on_rails/locales/base.rb +150 -0
  66. data/lib/react_on_rails/locales/to_js.rb +37 -0
  67. data/lib/react_on_rails/locales/to_json.rb +27 -0
  68. data/lib/react_on_rails/prerender_error.rb +11 -15
  69. data/lib/react_on_rails/react_component/render_options.rb +4 -0
  70. data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +41 -46
  71. data/lib/react_on_rails/test_helper/ensure_assets_compiled.rb +7 -8
  72. data/lib/react_on_rails/utils.rb +14 -19
  73. data/lib/react_on_rails/version.rb +1 -1
  74. data/lib/react_on_rails/version_checker.rb +1 -0
  75. data/lib/react_on_rails/webpacker_utils.rb +13 -2
  76. data/lib/tasks/assets.rake +19 -44
  77. data/lib/tasks/locale.rake +4 -2
  78. data/package-scripts.yml +11 -8
  79. data/package.json +29 -28
  80. data/rakelib/dummy_apps.rake +1 -9
  81. data/rakelib/example_type.rb +3 -1
  82. data/rakelib/examples.rake +3 -0
  83. data/rakelib/lint.rake +2 -7
  84. data/rakelib/node_package.rake +2 -2
  85. data/rakelib/release.rake +0 -6
  86. data/rakelib/run_rspec.rake +5 -18
  87. data/react_on_rails.gemspec +3 -5
  88. data/tsconfig.json +14 -0
  89. data/webpackConfigLoader.js +3 -2
  90. data/yarn.lock +4170 -2197
  91. metadata +34 -57
  92. data/Gemfile.rails32 +0 -73
  93. data/docs/additional-reading/babel.md +0 -5
  94. data/docs/additional-reading/hot-reloading-rails-development-asset-pipeline.md +0 -47
  95. data/docs/api/ruby-api-hot-reload-view-helpers.md +0 -44
  96. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-server +0 -12
  97. data/lib/react_on_rails/assets_precompile.rb +0 -153
  98. data/lib/react_on_rails/locales_to_js.rb +0 -138
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ReactOnRails
2
4
  class JsonParseError < ::ReactOnRails::Error
3
5
  attr_reader :json
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module ReactOnRails
6
+ module Locales
7
+ def self.compile
8
+ if ReactOnRails.configuration.i18n_output_format&.downcase == "js"
9
+ ReactOnRails::Locales::ToJs.new
10
+ else
11
+ ReactOnRails::Locales::ToJson.new
12
+ end
13
+ end
14
+
15
+ class Base
16
+ def initialize
17
+ return if i18n_dir.nil?
18
+ return unless obsolete?
19
+
20
+ @translations, @defaults = generate_translations
21
+ convert
22
+ end
23
+
24
+ private
25
+
26
+ def file_format; end
27
+
28
+ def obsolete?
29
+ return true if exist_files.empty?
30
+
31
+ files_are_outdated
32
+ end
33
+
34
+ def exist_files
35
+ @exist_files ||= files.select(&File.method(:exist?))
36
+ end
37
+
38
+ def files_are_outdated
39
+ latest_yml = locale_files.map(&File.method(:mtime)).max
40
+ earliest = exist_files.map(&File.method(:mtime)).min
41
+ latest_yml > earliest
42
+ end
43
+
44
+ def file_names
45
+ %w[translations default]
46
+ end
47
+
48
+ def files
49
+ @files ||= file_names.map { |n| file(n) }
50
+ end
51
+
52
+ def file(name)
53
+ "#{i18n_dir}/#{name}.#{file_format}"
54
+ end
55
+
56
+ def locale_files
57
+ @locale_files ||= begin
58
+ if i18n_yml_dir.present?
59
+ Dir["#{i18n_yml_dir}/**/*.yml"]
60
+ else
61
+ ReactOnRails::Utils.truthy_presence(
62
+ Rails.application && Rails.application.config.i18n.load_path
63
+ ).presence
64
+ end
65
+ end
66
+ end
67
+
68
+ def i18n_dir
69
+ @i18n_dir ||= ReactOnRails.configuration.i18n_dir
70
+ end
71
+
72
+ def i18n_yml_dir
73
+ @i18n_yml_dir ||= ReactOnRails.configuration.i18n_yml_dir
74
+ end
75
+
76
+ def default_locale
77
+ @default_locale ||= I18n.default_locale.to_s || "en"
78
+ end
79
+
80
+ def convert
81
+ file_names.each do |name|
82
+ template = send("template_#{name}")
83
+ path = file(name)
84
+ generate_file(template, path)
85
+ end
86
+ end
87
+
88
+ def generate_file(template, path)
89
+ result = ERB.new(template).result()
90
+ File.open(path, "w") do |f|
91
+ f.write(result)
92
+ end
93
+ end
94
+
95
+ def generate_translations
96
+ translations = {}
97
+ defaults = {}
98
+ locale_files.each do |f|
99
+ translation = YAML.safe_load(File.open(f))
100
+ key = translation.keys[0]
101
+ val = flatten(translation[key])
102
+ translations = translations.deep_merge(key => val)
103
+ defaults = defaults.deep_merge(flatten_defaults(val)) if key == default_locale
104
+ end
105
+ [translations.to_json, defaults.to_json]
106
+ end
107
+
108
+ def format(input)
109
+ input.to_s.tr(".", "_").camelize(:lower).to_sym
110
+ end
111
+
112
+ def flatten_defaults(val)
113
+ flatten(val).each_with_object({}) do |(k, v), h|
114
+ key = format(k)
115
+ h[key] = { id: k, defaultMessage: v }
116
+ end
117
+ end
118
+
119
+ def flatten(translations)
120
+ translations.each_with_object({}) do |(k, v), h|
121
+ if v.is_a? Hash
122
+ flatten(v).map { |hk, hv| h["#{k}.#{hk}".to_sym] = hv }
123
+ elsif v.is_a?(String)
124
+ h[k] = v.gsub("%{", "{")
125
+ elsif !v.is_a?(Array)
126
+ h[k] = v
127
+ end
128
+ end
129
+ end
130
+
131
+ def template_translations
132
+ <<-JS.strip_heredoc
133
+ export const translations = #{@translations};
134
+ JS
135
+ end
136
+
137
+ def template_default
138
+ <<-JS.strip_heredoc
139
+ import { defineMessages } from 'react-intl';
140
+
141
+ const defaultLocale = \'#{default_locale}\';
142
+
143
+ const defaultMessages = defineMessages(#{@defaults});
144
+
145
+ export { defaultMessages, defaultLocale };
146
+ JS
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module ReactOnRails
6
+ module Locales
7
+ class ToJs < Base
8
+ def initialize
9
+ super
10
+ end
11
+
12
+ private
13
+
14
+ def file_format
15
+ "js"
16
+ end
17
+
18
+ def template_translations
19
+ <<-JS.strip_heredoc
20
+ export const translations = #{@translations};
21
+ JS
22
+ end
23
+
24
+ def template_default
25
+ <<-JS.strip_heredoc
26
+ import { defineMessages } from 'react-intl';
27
+
28
+ const defaultLocale = \'#{default_locale}\';
29
+
30
+ const defaultMessages = defineMessages(#{@defaults});
31
+
32
+ export { defaultMessages, defaultLocale };
33
+ JS
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module ReactOnRails
6
+ module Locales
7
+ class ToJson < Base
8
+ def initialize
9
+ super
10
+ end
11
+
12
+ private
13
+
14
+ def file_format
15
+ "json"
16
+ end
17
+
18
+ def template_translations
19
+ @translations
20
+ end
21
+
22
+ def template_default
23
+ @defaults
24
+ end
25
+ end
26
+ end
27
+ end
@@ -48,33 +48,29 @@ module ReactOnRails
48
48
  def calc_message(component_name, console_messages, err, js_code, props)
49
49
  message = "ERROR in SERVER PRERENDERING\n".dup
50
50
  if err
51
- # rubocop:disable Layout/IndentHeredoc
52
- message << <<-MSG
53
- Encountered error: \"#{err}\"
51
+ message << <<~MSG
52
+ Encountered error: \"#{err}\"
54
53
  MSG
55
- # rubocop:enable Layout/IndentHeredoc
54
+
56
55
  backtrace = err.backtrace.join("\n")
57
56
  else
58
57
  backtrace = nil
59
58
  end
60
- # rubocop:disable Layout/IndentHeredoc
61
- message << <<-MSG
62
- when prerendering #{component_name} with props: #{Utils.smart_trim(props, MAX_ERROR_SNIPPET_TO_LOG)}
59
+ message << <<~MSG
60
+ when prerendering #{component_name} with props: #{Utils.smart_trim(props, MAX_ERROR_SNIPPET_TO_LOG)}
63
61
 
64
- code:
62
+ code:
65
63
 
66
- #{Utils.smart_trim(js_code, MAX_ERROR_SNIPPET_TO_LOG)}
64
+ #{Utils.smart_trim(js_code, MAX_ERROR_SNIPPET_TO_LOG)}
67
65
 
68
66
  MSG
69
- # rubocop:enable Layout/IndentHeredoc
70
67
 
71
68
  if console_messages
72
- # rubocop:disable Layout/IndentHeredoc
73
- message << <<-MSG
74
- console messages:
75
- #{console_messages}
69
+ message << <<~MSG
70
+ console messages:
71
+ #{console_messages}
76
72
  MSG
77
- # rubocop:enable Layout/IndentHeredoc
73
+
78
74
  end
79
75
  [backtrace, message]
80
76
  end
@@ -72,6 +72,10 @@ module ReactOnRails
72
72
  "{ react_component_name = #{react_component_name}, options = #{options}, request_digest = #{request_digest}"
73
73
  end
74
74
 
75
+ def internal_option(key)
76
+ options[key]
77
+ end
78
+
75
79
  private
76
80
 
77
81
  attr_reader :options
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "open-uri"
4
+ require "execjs"
4
5
 
5
6
  module ReactOnRails
6
7
  module ServerRenderingPool
@@ -121,14 +122,12 @@ module ReactOnRails
121
122
  return if ReactOnRails.configuration.server_bundle_js_file.blank?
122
123
 
123
124
  bundle_js_code = read_bundle_js_code
124
-
125
- # rubocop:disable Layout/IndentHeredoc
126
- base_js_code = <<-JS
127
- #{console_polyfill}
128
- #{execjs_timer_polyfills}
129
- #{bundle_js_code};
125
+ base_js_code = <<~JS
126
+ #{console_polyfill}
127
+ #{execjs_timer_polyfills}
128
+ #{bundle_js_code};
130
129
  JS
131
- # rubocop:enable Layout/IndentHeredoc
130
+
132
131
  file_name = "tmp/base_js_code.js"
133
132
  begin
134
133
  if ReactOnRails.configuration.trace
@@ -151,33 +150,31 @@ module ReactOnRails
151
150
  end
152
151
 
153
152
  def execjs_timer_polyfills
154
- # rubocop:disable Layout/IndentHeredoc
155
- <<-JS
156
- function getStackTrace () {
157
- var stack;
158
- try {
159
- throw new Error('');
160
- }
161
- catch (error) {
162
- stack = error.stack || '';
163
- }
164
- stack = stack.split('\\n').map(function (line) { return line.trim(); });
165
- return stack.splice(stack[0] == 'Error' ? 2 : 1);
166
- }
167
-
168
- function setInterval() {
169
- #{undefined_for_exec_js_logging('setInterval')}
170
- }
171
-
172
- function setTimeout() {
173
- #{undefined_for_exec_js_logging('setTimeout')}
174
- }
175
-
176
- function clearTimeout() {
177
- #{undefined_for_exec_js_logging('clearTimeout')}
178
- }
153
+ <<~JS
154
+ function getStackTrace () {
155
+ var stack;
156
+ try {
157
+ throw new Error('');
158
+ }
159
+ catch (error) {
160
+ stack = error.stack || '';
161
+ }
162
+ stack = stack.split('\\n').map(function (line) { return line.trim(); });
163
+ return stack.splice(stack[0] == 'Error' ? 2 : 1);
164
+ }
165
+
166
+ function setInterval() {
167
+ #{undefined_for_exec_js_logging('setInterval')}
168
+ }
169
+
170
+ function setTimeout() {
171
+ #{undefined_for_exec_js_logging('setTimeout')}
172
+ }
173
+
174
+ function clearTimeout() {
175
+ #{undefined_for_exec_js_logging('clearTimeout')}
176
+ }
179
177
  JS
180
- # rubocop:enable Layout/IndentHeredoc
181
178
  end
182
179
 
183
180
  def undefined_for_exec_js_logging(function_name)
@@ -191,20 +188,18 @@ function clearTimeout() {
191
188
 
192
189
  # Reimplement console methods for replaying on the client
193
190
  def console_polyfill
194
- # rubocop:disable Layout/IndentHeredoc
195
- <<-JS
196
- var console = { history: [] };
197
- ['error', 'log', 'info', 'warn'].forEach(function (level) {
198
- console[level] = function () {
199
- var argArray = Array.prototype.slice.call(arguments);
200
- if (argArray.length > 0) {
201
- argArray[0] = '[SERVER] ' + argArray[0];
202
- }
203
- console.history.push({level: level, arguments: argArray});
204
- };
205
- });
191
+ <<~JS
192
+ var console = { history: [] };
193
+ ['error', 'log', 'info', 'warn'].forEach(function (level) {
194
+ console[level] = function () {
195
+ var argArray = Array.prototype.slice.call(arguments);
196
+ if (argArray.length > 0) {
197
+ argArray[0] = '[SERVER] ' + argArray[0];
198
+ }
199
+ console.history.push({level: level, arguments: argArray});
200
+ };
201
+ });
206
202
  JS
207
- # rubocop:enable Layout/IndentHeredoc
208
203
  end
209
204
 
210
205
  private
@@ -5,6 +5,7 @@ module ReactOnRails
5
5
  class EnsureAssetsCompiled
6
6
  class << self
7
7
  attr_accessor :has_been_run
8
+
8
9
  @has_been_run = false
9
10
  end
10
11
 
@@ -29,7 +30,7 @@ module ReactOnRails
29
30
  # Be sure we don't do this again.
30
31
  self.class.has_been_run = true
31
32
 
32
- ReactOnRails::LocalesToJs.new
33
+ ReactOnRails::Locales.compile
33
34
 
34
35
  stale_gen_files = webpack_assets_status_checker.stale_generated_webpack_files
35
36
 
@@ -43,17 +44,15 @@ module ReactOnRails
43
44
  end
44
45
 
45
46
  def puts_start_compile_check_message(stale_files)
46
- # rubocop:disable Layout/IndentHeredoc
47
- puts <<-MSG
47
+ puts <<~MSG
48
48
 
49
- Detected the following stale generated files:
50
- #{stale_files.join("\n ")}
49
+ Detected the following stale generated files:
50
+ #{stale_files.join("\n ")}
51
51
 
52
- React on Rails will ensure your JavaScript generated files are up to date, using your
53
- `#{ReactOnRails::Utils.prepend_cd_node_modules_directory(ReactOnRails.configuration.build_test_command)}` command.
52
+ React on Rails will ensure your JavaScript generated files are up to date, using your
53
+ `#{ReactOnRails::Utils.prepend_cd_node_modules_directory(ReactOnRails.configuration.build_test_command)}` command.
54
54
 
55
55
  MSG
56
- # rubocop:enable Layout/IndentHeredoc
57
56
  end
58
57
  end
59
58
  end
@@ -8,7 +8,7 @@ require "active_support/core_ext/string"
8
8
 
9
9
  module ReactOnRails
10
10
  module Utils
11
- TRUNCATION_FILLER = "\n... TRUNCATED ...\n".freeze
11
+ TRUNCATION_FILLER = "\n... TRUNCATED ...\n"
12
12
 
13
13
  # https://forum.shakacode.com/t/yak-of-the-week-ruby-2-4-pathname-empty-changed-to-look-at-file-size/901
14
14
  # return object if truthy, else return nil
@@ -24,13 +24,12 @@ module ReactOnRails
24
24
  # Pass in the msg and color as a symbol.
25
25
  def self.wrap_message(msg, color = :red)
26
26
  wrapper_line = ("=" * 80).to_s
27
- # rubocop:disable Layout/IndentHeredoc
28
- fenced_msg = <<-MSG
29
- #{wrapper_line}
30
- #{msg.strip}
31
- #{wrapper_line}
27
+ fenced_msg = <<~MSG
28
+ #{wrapper_line}
29
+ #{msg.strip}
30
+ #{wrapper_line}
32
31
  MSG
33
- # rubocop:enable Layout/IndentHeredoc
32
+
34
33
  Rainbow(fenced_msg).color(color)
35
34
  end
36
35
 
@@ -48,14 +47,13 @@ module ReactOnRails
48
47
  unless status.success?
49
48
  stdout_msg = stdout.present? ? "\nstdout:\n#{stdout.strip}\n" : ""
50
49
  stderr_msg = stderr.present? ? "\nstderr:\n#{stderr.strip}\n" : ""
51
- # rubocop:disable Layout/IndentHeredoc
52
- msg = <<-MSG
53
- React on Rails FATAL ERROR!
54
- #{failure_message}
55
- cmd: #{cmd}
56
- exitstatus: #{status.exitstatus}#{stdout_msg}#{stderr_msg}
50
+ msg = <<~MSG
51
+ React on Rails FATAL ERROR!
52
+ #{failure_message}
53
+ cmd: #{cmd}
54
+ exitstatus: #{status.exitstatus}#{stdout_msg}#{stderr_msg}
57
55
  MSG
58
- # rubocop:enable Layout/IndentHeredoc
56
+
59
57
  puts wrap_message(msg)
60
58
 
61
59
  # Rspec catches exit without! in the exit callbacks
@@ -72,11 +70,8 @@ exitstatus: #{status.exitstatus}#{stdout_msg}#{stderr_msg}
72
70
  # Either:
73
71
  # 1. Using same bundle for both server and client, so server bundle will be hashed in manifest
74
72
  # 2. Using a different bundle (different Webpack config), so file is not hashed, and
75
- # bundle_js_path will throw.
76
- # 3. Not using webpacker, and bundle_js_path always returns
77
-
78
- # Note, server bundle should not be in the manifest
79
- # If using webpacker gem per https://github.com/rails/webpacker/issues/571
73
+ # bundle_js_path will throw so the default path is used without a hash.
74
+ # 3. Not using webpacker, and this method returns the bundle_js_file_path
80
75
  return @server_bundle_path if @server_bundle_path && !Rails.env.development?
81
76
 
82
77
  bundle_name = ReactOnRails.configuration.server_bundle_js_file