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
@@ -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"
|
49
|
+
message = +"ERROR in SERVER PRERENDERING\n"
|
34
50
|
if err
|
35
|
-
|
36
|
-
|
37
|
-
|
51
|
+
message << <<~MSG
|
52
|
+
Encountered error:
|
53
|
+
|
54
|
+
#{err}
|
55
|
+
|
38
56
|
MSG
|
39
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
#{console_messages}
|
72
|
+
message << <<~MSG
|
73
|
+
console messages:
|
74
|
+
#{console_messages}
|
57
75
|
MSG
|
58
|
-
|
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)
|
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
|
-
|
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
|
-
|
79
|
+
retrieve_configuration_value_for(:trace)
|
37
80
|
end
|
38
81
|
|
39
82
|
def replay_console
|
40
|
-
|
83
|
+
retrieve_configuration_value_for(:replay_console)
|
41
84
|
end
|
42
85
|
|
43
86
|
def raise_on_prerender_error
|
44
|
-
|
87
|
+
retrieve_configuration_value_for(:raise_on_prerender_error)
|
45
88
|
end
|
46
89
|
|
47
90
|
def logging_on_server
|
48
|
-
|
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
|
-
"#{
|
115
|
+
"#{base_dom_id}-#{SecureRandom.uuid}"
|
61
116
|
end
|
62
117
|
|
63
|
-
def
|
118
|
+
def retrieve_configuration_value_for(key)
|
64
119
|
options.fetch(key) do
|
65
120
|
ReactOnRails.configuration.public_send(key)
|
66
121
|
end
|