react_on_rails 11.0.0 → 12.0.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.
Files changed (130) hide show
  1. checksums.yaml +5 -5
  2. data/.circleci/config.yml +320 -0
  3. data/.eslintignore +2 -1
  4. data/.eslintrc +30 -2
  5. data/.github/FUNDING.yml +1 -0
  6. data/.gitignore +3 -1
  7. data/.prettierignore +10 -0
  8. data/.prettierrc +23 -0
  9. data/.release-it.json +3 -0
  10. data/.rubocop.yml +39 -11
  11. data/.travis.yml +11 -22
  12. data/CHANGELOG.md +235 -6
  13. data/CONTRIBUTING.md +64 -84
  14. data/Gemfile +3 -5
  15. data/KUDOS.md +4 -1
  16. data/{docs/LICENSE.md → LICENSE.md} +1 -1
  17. data/PROJECTS.md +7 -1
  18. data/REACT-ON-RAILS-PRO-LICENSE +95 -0
  19. data/README.md +233 -630
  20. data/Rakefile +1 -5
  21. data/SUMMARY.md +42 -29
  22. data/book.json +5 -5
  23. data/docs/additional-reading/asset-pipeline.md +8 -16
  24. data/docs/additional-reading/convert-rails-5-api-only-app.md +19 -0
  25. data/docs/additional-reading/credits.md +10 -0
  26. data/docs/additional-reading/images.md +1 -1
  27. data/docs/additional-reading/rails-engine-integration.md +7 -0
  28. data/docs/additional-reading/rails_view_rendering_from_inline_javascript.md +2 -1
  29. data/docs/additional-reading/react-helmet.md +30 -10
  30. data/docs/additional-reading/react-router.md +52 -75
  31. data/docs/additional-reading/server-rendering-tips.md +14 -11
  32. data/docs/additional-reading/upgrade-webpacker-v3-to-v4.md +10 -0
  33. data/docs/additional-reading/webpack.md +2 -2
  34. data/docs/api/javascript-api.md +24 -4
  35. data/docs/api/redux-store-api.md +102 -0
  36. data/docs/api/view-helpers-api.md +133 -0
  37. data/docs/articles.md +20 -0
  38. data/docs/basics/client-vs-server-rendering.md +23 -0
  39. data/docs/basics/configuration.md +145 -61
  40. data/docs/basics/deployment.md +4 -0
  41. data/docs/basics/{generator.md → generator-details.md} +5 -8
  42. data/docs/basics/heroku-deployment.md +24 -0
  43. data/docs/basics/hmr-and-hot-reloading-with-the-webpack-dev-server.md +49 -0
  44. data/docs/basics/i18n.md +45 -23
  45. data/docs/basics/installation-into-an-existing-rails-app.md +59 -0
  46. data/docs/basics/minitest-configuration.md +31 -0
  47. data/docs/basics/react-server-rendering.md +29 -0
  48. data/docs/{additional-reading → basics}/recommended-project-structure.md +38 -10
  49. data/docs/basics/render-functions-and-railscontext.md +205 -0
  50. data/docs/basics/rspec-configuration.md +73 -0
  51. data/docs/basics/upgrading-react-on-rails.md +75 -4
  52. data/docs/basics/webpack-configuration.md +50 -0
  53. data/docs/contributor-info/errors-with-hooks.md +45 -0
  54. data/docs/contributor-info/pull-requests.md +44 -0
  55. data/docs/misc/doctrine.md +2 -2
  56. data/docs/{additional-reading → outdated}/code-splitting.md +19 -9
  57. data/docs/outdated/how-react-on-rails-works.md +44 -0
  58. data/docs/{basics/installation-overview.md → outdated/manual-installation-overview.md} +8 -13
  59. data/docs/{additional-reading → outdated}/rails-assets-relative-paths.md +3 -3
  60. data/docs/{additional-reading → outdated}/rails-assets.md +5 -13
  61. data/docs/testimonials/hvmn.md +25 -0
  62. data/docs/testimonials/resortpass.md +13 -0
  63. data/docs/testimonials/testimonials.md +28 -0
  64. data/docs/tutorial.md +182 -44
  65. data/jest.config.js +4 -0
  66. data/lib/generators/USAGE +1 -1
  67. data/lib/generators/react_on_rails/base_generator.rb +2 -2
  68. data/lib/generators/react_on_rails/dev_tests_generator.rb +3 -2
  69. data/lib/generators/react_on_rails/generator_helper.rb +4 -6
  70. data/lib/generators/react_on_rails/install_generator.rb +2 -0
  71. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev +3 -1
  72. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-hmr +26 -0
  73. data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +20 -40
  74. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb +4 -1
  75. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +4 -8
  76. data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.js +1 -3
  77. data/lib/react_on_rails/configuration.rb +175 -102
  78. data/lib/react_on_rails/error.rb +2 -0
  79. data/lib/react_on_rails/git_utils.rb +2 -0
  80. data/lib/react_on_rails/{react_on_rails_helper.rb → helper.rb} +201 -225
  81. data/lib/react_on_rails/json_output.rb +1 -1
  82. data/lib/react_on_rails/json_parse_error.rb +28 -0
  83. data/lib/react_on_rails/locales/base.rb +150 -0
  84. data/lib/react_on_rails/locales/to_js.rb +37 -0
  85. data/lib/react_on_rails/locales/to_json.rb +27 -0
  86. data/lib/react_on_rails/prerender_error.rb +56 -18
  87. data/lib/react_on_rails/react_component/render_options.rb +98 -0
  88. data/lib/react_on_rails/server_rendering_js_code.rb +42 -0
  89. data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +139 -106
  90. data/lib/react_on_rails/server_rendering_pool.rb +4 -16
  91. data/lib/react_on_rails/test_helper/ensure_assets_compiled.rb +7 -8
  92. data/lib/react_on_rails/test_helper/webpack_assets_compiler.rb +17 -0
  93. data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +10 -6
  94. data/lib/react_on_rails/test_helper.rb +18 -7
  95. data/lib/react_on_rails/utils.rb +66 -26
  96. data/lib/react_on_rails/version.rb +1 -1
  97. data/lib/react_on_rails/version_checker.rb +5 -1
  98. data/lib/react_on_rails/version_syntax_converter.rb +14 -12
  99. data/lib/react_on_rails/webpacker_utils.rb +44 -10
  100. data/lib/react_on_rails.rb +8 -3
  101. data/lib/tasks/assets.rake +33 -46
  102. data/lib/tasks/locale.rake +4 -2
  103. data/package-scripts.yml +49 -0
  104. data/package.json +49 -40
  105. data/rakelib/dummy_apps.rake +1 -9
  106. data/rakelib/example_type.rb +3 -1
  107. data/rakelib/examples.rake +3 -0
  108. data/rakelib/lint.rake +2 -7
  109. data/rakelib/node_package.rake +2 -2
  110. data/rakelib/release.rake +4 -8
  111. data/rakelib/run_rspec.rake +5 -18
  112. data/react_on_rails.gemspec +5 -6
  113. data/tsconfig.json +14 -0
  114. data/webpackConfigLoader.js +5 -4
  115. data/yarn.lock +7645 -1821
  116. metadata +71 -61
  117. data/Gemfile.rails32 +0 -74
  118. data/docs/additional-reading/babel.md +0 -5
  119. data/docs/additional-reading/caching-and-performance.md +0 -4
  120. data/docs/additional-reading/heroku-deployment.md +0 -92
  121. data/docs/additional-reading/hot-reloading-rails-development.md +0 -57
  122. data/docs/additional-reading/node-server-rendering.md +0 -5
  123. data/docs/additional-reading/rspec-configuration.md +0 -56
  124. data/docs/api/ruby-api-hot-reload-view-helpers.md +0 -44
  125. data/docs/api/ruby-api.md +0 -8
  126. data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-server +0 -12
  127. data/lib/react_on_rails/assets_precompile.rb +0 -150
  128. data/lib/react_on_rails/locales_to_js.rb +0 -134
  129. data/lib/react_on_rails/react_component/options.rb +0 -64
  130. /data/docs/{misc → outdated}/rails3.md +0 -0
@@ -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
@@ -2,39 +2,77 @@
2
2
 
3
3
  # rubocop:disable: Layout/IndentHeredoc
4
4
  module ReactOnRails
5
- class PrerenderError < StandardError
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/
9
+ attr_reader :component_name, :err, :props, :js_code, :console_messages
10
+
6
11
  # err might be nil if JS caught the error
7
12
  def initialize(component_name: nil, err: nil, props: nil,
8
13
  js_code: nil, console_messages: nil)
14
+ @component_name = component_name
15
+ @err = err
16
+ @props = props
17
+ @js_code = js_code
18
+ @console_messages = console_messages
19
+
20
+ backtrace, message = calc_message(component_name, console_messages, err, js_code, props)
21
+
22
+ super([message, backtrace].compact.join("\n"))
23
+ end
24
+
25
+ def to_honeybadger_context
26
+ to_error_context
27
+ end
28
+
29
+ def raven_context
30
+ to_error_context
31
+ end
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
+
46
+ private
47
+
48
+ def calc_message(component_name, console_messages, err, js_code, props)
9
49
  message = "ERROR in SERVER PRERENDERING\n".dup
10
50
  if err
11
- # rubocop:disable Layout/IndentHeredoc
12
- message << <<-MSG
13
- Encountered error: \"#{err}\"
51
+ message << <<~MSG
52
+ Encountered error: \"#{err}\"
14
53
  MSG
15
- # rubocop:enable Layout/IndentHeredoc
54
+
16
55
  backtrace = err.backtrace.join("\n")
17
56
  else
18
57
  backtrace = nil
19
58
  end
20
- # rubocop:disable Layout/IndentHeredoc
21
- message << <<-MSG
22
- when prerendering #{component_name} with props: #{props}
23
- js_code was:
24
- #{js_code}
59
+ message << <<~MSG
60
+ when prerendering #{component_name} with props: #{Utils.smart_trim(props, MAX_ERROR_SNIPPET_TO_LOG)}
61
+
62
+ code:
63
+
64
+ #{Utils.smart_trim(js_code, MAX_ERROR_SNIPPET_TO_LOG)}
65
+
25
66
  MSG
26
- # rubocop:enable Layout/IndentHeredoc
27
67
 
28
68
  if console_messages
29
- # rubocop:disable Layout/IndentHeredoc
30
- message << <<-MSG
31
- console messages:
32
- #{console_messages}
69
+ message << <<~MSG
70
+ console messages:
71
+ #{console_messages}
33
72
  MSG
34
- # rubocop:enable Layout/IndentHeredoc
35
- end
36
73
 
37
- super([message, backtrace].compact.join("\n"))
74
+ end
75
+ [backtrace, message]
38
76
  end
39
77
  end
40
78
  end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "react_on_rails/utils"
4
+
5
+ module ReactOnRails
6
+ module ReactComponent
7
+ class RenderOptions
8
+ include Utils::Required
9
+
10
+ attr_accessor :request_digest
11
+
12
+ NO_PROPS = {}.freeze
13
+
14
+ def initialize(react_component_name: required("react_component_name"), options: required("options"))
15
+ @react_component_name = react_component_name.camelize
16
+ @options = options
17
+ end
18
+
19
+ attr_reader :react_component_name
20
+
21
+ def props
22
+ options.fetch(:props) { NO_PROPS }
23
+ end
24
+
25
+ def random_dom_id
26
+ retrieve_key(:random_dom_id)
27
+ end
28
+
29
+ def dom_id
30
+ @dom_id ||= options.fetch(:id) do
31
+ if random_dom_id
32
+ generate_unique_dom_id
33
+ else
34
+ base_dom_id
35
+ end
36
+ end
37
+ end
38
+
39
+ def random_dom_id?
40
+ return false if options[:id]
41
+
42
+ return false unless random_dom_id
43
+
44
+ true
45
+ end
46
+
47
+ def html_options
48
+ options[:html_options].to_h
49
+ end
50
+
51
+ def prerender
52
+ retrieve_key(:prerender)
53
+ end
54
+
55
+ def trace
56
+ retrieve_key(:trace)
57
+ end
58
+
59
+ def replay_console
60
+ retrieve_key(:replay_console)
61
+ end
62
+
63
+ def raise_on_prerender_error
64
+ retrieve_key(:raise_on_prerender_error)
65
+ end
66
+
67
+ def logging_on_server
68
+ retrieve_key(:logging_on_server)
69
+ end
70
+
71
+ def to_s
72
+ "{ react_component_name = #{react_component_name}, options = #{options}, request_digest = #{request_digest}"
73
+ end
74
+
75
+ def internal_option(key)
76
+ options[key]
77
+ end
78
+
79
+ private
80
+
81
+ attr_reader :options
82
+
83
+ def base_dom_id
84
+ "#{react_component_name}-react-component"
85
+ end
86
+
87
+ def generate_unique_dom_id
88
+ "#{base_dom_id}-#{SecureRandom.uuid}"
89
+ end
90
+
91
+ def retrieve_key(key)
92
+ options.fetch(key) do
93
+ ReactOnRails.configuration.public_send(key)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ReactOnRails
4
+ module ServerRenderingJsCode
5
+ class << self
6
+ def js_code_renderer
7
+ @js_code_renderer ||= if ReactOnRails::Utils.react_on_rails_pro?
8
+ ReactOnRailsPro::ServerRenderingJsCode
9
+ else
10
+ self
11
+ end
12
+ end
13
+
14
+ def server_rendering_component_js_code(
15
+ props_string: nil,
16
+ rails_context: nil,
17
+ redux_stores: nil,
18
+ react_component_name: nil,
19
+ render_options: nil
20
+ )
21
+ js_code_renderer.render(props_string, rails_context, redux_stores, react_component_name, render_options)
22
+ end
23
+
24
+ def render(props_string, rails_context, redux_stores, react_component_name, render_options)
25
+ <<-JS
26
+ (function() {
27
+ var railsContext = #{rails_context};
28
+ #{redux_stores}
29
+ var props = #{props_string};
30
+ return ReactOnRails.serverRenderReactComponent({
31
+ name: '#{react_component_name}',
32
+ domNodeId: '#{render_options.dom_id}',
33
+ props: props,
34
+ trace: #{render_options.trace},
35
+ railsContext: railsContext
36
+ });
37
+ })()
38
+ JS
39
+ end
40
+ end
41
+ end
42
+ end