react_on_rails 14.2.1 → 16.1.1
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.
- checksums.yaml +4 -4
- data/AI_AGENT_INSTRUCTIONS.md +63 -0
- data/CHANGELOG.md +564 -85
- data/CLAUDE.md +135 -0
- data/CODING_AGENTS.md +313 -0
- data/CONTRIBUTING.md +448 -37
- data/Gemfile.development_dependencies +6 -1
- data/Gemfile.lock +13 -4
- data/KUDOS.md +22 -1
- data/LICENSE.md +30 -4
- data/LICENSES/README.md +14 -0
- data/NEWS.md +48 -48
- data/PROJECTS.md +45 -40
- data/REACT-ON-RAILS-PRO-LICENSE.md +129 -0
- data/README.md +113 -42
- data/SUMMARY.md +62 -52
- data/TODO.md +135 -0
- data/bin/lefthook/check-trailing-newlines +38 -0
- data/bin/lefthook/get-changed-files +26 -0
- data/bin/lefthook/prettier-format +26 -0
- data/bin/lefthook/ruby-autofix +26 -0
- data/bin/lefthook/ruby-lint +27 -0
- data/eslint.config.ts +232 -0
- data/knip.ts +40 -6
- data/lib/generators/USAGE +4 -5
- data/lib/generators/react_on_rails/USAGE +65 -0
- data/lib/generators/react_on_rails/base_generator.rb +276 -62
- data/lib/generators/react_on_rails/dev_tests_generator.rb +1 -0
- data/lib/generators/react_on_rails/generator_helper.rb +35 -1
- data/lib/generators/react_on_rails/generator_messages.rb +138 -17
- data/lib/generators/react_on_rails/install_generator.rb +474 -26
- data/lib/generators/react_on_rails/react_no_redux_generator.rb +19 -6
- data/lib/generators/react_on_rails/react_with_redux_generator.rb +110 -18
- data/lib/generators/react_on_rails/templates/.eslintrc +1 -1
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev +5 -0
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-prod-assets +8 -0
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static-assets +2 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -5
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +2 -2
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/bundles/HelloWorld/components/HelloWorldServer.js +1 -1
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/server-bundle.js +1 -8
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx +21 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.tsx +25 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css +4 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.jsx +5 -0
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.server.tsx +5 -0
- data/lib/generators/react_on_rails/templates/base/base/app/views/hello_world/index.html.erb.tt +1 -1
- data/lib/generators/react_on_rails/templates/base/base/app/views/layouts/hello_world.html.erb +4 -2
- data/lib/generators/react_on_rails/templates/base/base/babel.config.js.tt +5 -2
- data/lib/generators/react_on_rails/templates/base/base/bin/dev +34 -0
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +14 -5
- data/lib/generators/react_on_rails/templates/base/base/config/shakapacker.yml +76 -7
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/commonWebpackConfig.js.tt +1 -1
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/development.js.tt +6 -10
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/production.js.tt +2 -2
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt +3 -2
- data/lib/generators/react_on_rails/templates/base/base/config/webpack/test.js.tt +2 -2
- data/lib/generators/react_on_rails/templates/dev_tests/spec/system/hello_world_spec.rb +0 -2
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.ts +18 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx +0 -6
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.module.css +4 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/components/HelloWorld.tsx +24 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/constants/helloWorldConstants.ts +6 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/containers/HelloWorldContainer.ts +20 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.js +1 -1
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.ts +22 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.client.tsx +23 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.jsx +5 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.server.tsx +5 -0
- data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/store/helloWorldStore.ts +18 -0
- data/lib/react_on_rails/configuration.rb +141 -57
- data/lib/react_on_rails/controller.rb +6 -2
- data/lib/react_on_rails/dev/file_manager.rb +78 -0
- data/lib/react_on_rails/dev/pack_generator.rb +27 -0
- data/lib/react_on_rails/dev/process_manager.rb +61 -0
- data/lib/react_on_rails/dev/server_manager.rb +487 -0
- data/lib/react_on_rails/dev.rb +20 -0
- data/lib/react_on_rails/doctor.rb +1149 -0
- data/lib/react_on_rails/engine.rb +6 -0
- data/lib/react_on_rails/git_utils.rb +12 -2
- data/lib/react_on_rails/helper.rb +176 -74
- data/lib/react_on_rails/json_parse_error.rb +6 -1
- data/lib/react_on_rails/packer_utils.rb +61 -71
- data/lib/react_on_rails/packs_generator.rb +221 -19
- data/lib/react_on_rails/prerender_error.rb +4 -0
- data/lib/react_on_rails/pro/NOTICE +21 -0
- data/lib/react_on_rails/pro/helper.rb +122 -0
- data/lib/react_on_rails/pro/utils.rb +53 -0
- data/lib/react_on_rails/react_component/render_options.rb +38 -6
- data/lib/react_on_rails/server_rendering_js_code.rb +0 -1
- data/lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb +12 -5
- data/lib/react_on_rails/system_checker.rb +659 -0
- data/lib/react_on_rails/test_helper/webpack_assets_compiler.rb +1 -1
- data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +6 -4
- data/lib/react_on_rails/test_helper.rb +2 -3
- data/lib/react_on_rails/utils.rb +139 -43
- data/lib/react_on_rails/version.rb +1 -1
- data/lib/react_on_rails/version_checker.rb +14 -20
- data/lib/react_on_rails/version_syntax_converter.rb +1 -1
- data/lib/react_on_rails.rb +1 -0
- data/lib/tasks/assets.rake +1 -1
- data/lib/tasks/doctor.rake +48 -0
- data/lib/tasks/generate_packs.rake +158 -1
- data/react_on_rails.gemspec +1 -0
- data/tsconfig.eslint.json +6 -0
- data/tsconfig.json +5 -3
- metadata +63 -14
- data/REACT-ON-RAILS-PRO-LICENSE +0 -95
- data/lib/generators/react_on_rails/adapt_for_older_shakapacker_generator.rb +0 -41
- data/lib/generators/react_on_rails/bin/dev +0 -30
- data/lib/generators/react_on_rails/bin/dev-static +0 -30
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev-static.tt +0 -9
- data/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt +0 -5
- data/lib/generators/react_on_rails/templates/base/base/app/javascript/packs/registration.js.tt +0 -8
- /data/lib/generators/react_on_rails/templates/base/base/config/webpack/{webpackConfig.js.tt → generateWebpackConfigs.js.tt} +0 -0
- /data/lib/generators/react_on_rails/templates/redux/base/app/javascript/bundles/HelloWorld/startup/{HelloWorldApp.jsx → HelloWorldApp.client.jsx} +0 -0
@@ -1,12 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rails/generators"
|
4
|
+
require "fileutils"
|
4
5
|
require_relative "generator_messages"
|
5
6
|
require_relative "generator_helper"
|
6
7
|
module ReactOnRails
|
7
8
|
module Generators
|
8
9
|
class BaseGenerator < Rails::Generators::Base
|
9
10
|
include GeneratorHelper
|
11
|
+
|
10
12
|
Rails::Generators.hide_namespace(namespace)
|
11
13
|
source_root(File.expand_path("templates", __dir__))
|
12
14
|
|
@@ -14,7 +16,7 @@ module ReactOnRails
|
|
14
16
|
class_option :redux,
|
15
17
|
type: :boolean,
|
16
18
|
default: false,
|
17
|
-
desc: "Install Redux
|
19
|
+
desc: "Install Redux package and Redux version of Hello World Example",
|
18
20
|
aliases: "-R"
|
19
21
|
|
20
22
|
def add_hello_world_route
|
@@ -22,28 +24,35 @@ module ReactOnRails
|
|
22
24
|
end
|
23
25
|
|
24
26
|
def create_react_directories
|
25
|
-
|
26
|
-
|
27
|
+
# Create auto-bundling directory structure for non-Redux components only
|
28
|
+
# Redux components handle their own directory structure
|
29
|
+
return if options.redux?
|
30
|
+
|
31
|
+
empty_directory("app/javascript/src/HelloWorld/ror_components")
|
27
32
|
end
|
28
33
|
|
29
34
|
def copy_base_files
|
30
35
|
base_path = "base/base/"
|
31
36
|
base_files = %w[app/controllers/hello_world_controller.rb
|
32
|
-
app/views/layouts/hello_world.html.erb
|
33
|
-
|
34
|
-
|
35
|
-
|
37
|
+
app/views/layouts/hello_world.html.erb
|
38
|
+
Procfile.dev
|
39
|
+
Procfile.dev-static-assets
|
40
|
+
Procfile.dev-prod-assets]
|
41
|
+
base_templates = %w[config/initializers/react_on_rails.rb]
|
36
42
|
base_files.each { |file| copy_file("#{base_path}#{file}", file) }
|
37
43
|
base_templates.each do |file|
|
38
|
-
template("#{base_path}/#{file}.tt", file
|
44
|
+
template("#{base_path}/#{file}.tt", file)
|
39
45
|
end
|
40
46
|
end
|
41
47
|
|
42
48
|
def copy_js_bundle_files
|
43
49
|
base_path = "base/base/"
|
44
|
-
base_files = %w[app/javascript/packs/server-bundle.js
|
45
|
-
|
46
|
-
|
50
|
+
base_files = %w[app/javascript/packs/server-bundle.js]
|
51
|
+
|
52
|
+
# Only copy HelloWorld.module.css for non-Redux components
|
53
|
+
# Redux components handle their own CSS files
|
54
|
+
base_files << "app/javascript/src/HelloWorld/ror_components/HelloWorld.module.css" unless options.redux?
|
55
|
+
|
47
56
|
base_files.each { |file| copy_file("#{base_path}#{file}", file) }
|
48
57
|
end
|
49
58
|
|
@@ -57,15 +66,25 @@ module ReactOnRails
|
|
57
66
|
config/webpack/development.js
|
58
67
|
config/webpack/production.js
|
59
68
|
config/webpack/serverWebpackConfig.js
|
60
|
-
config/webpack/
|
61
|
-
config/webpack/webpackConfig.js]
|
69
|
+
config/webpack/generateWebpackConfigs.js]
|
62
70
|
config = {
|
63
71
|
message: "// The source code including full typescript support is available at:"
|
64
72
|
}
|
65
73
|
base_files.each { |file| template("#{base_path}/#{file}.tt", file, config) }
|
74
|
+
|
75
|
+
# Handle webpack.config.js separately with smart replacement
|
76
|
+
copy_webpack_main_config(base_path, config)
|
66
77
|
end
|
67
78
|
|
68
79
|
def copy_packer_config
|
80
|
+
# Skip copying if Shakapacker was just installed (to avoid conflicts)
|
81
|
+
# Check for a temporary marker file that indicates fresh Shakapacker install
|
82
|
+
if File.exist?(".shakapacker_just_installed")
|
83
|
+
puts "Skipping Shakapacker config copy (already installed by Shakapacker installer)"
|
84
|
+
File.delete(".shakapacker_just_installed") # Clean up marker
|
85
|
+
return
|
86
|
+
end
|
87
|
+
|
69
88
|
puts "Adding Shakapacker #{ReactOnRails::PackerUtils.shakapacker_version} config"
|
70
89
|
base_path = "base/base/"
|
71
90
|
config = "config/shakapacker.yml"
|
@@ -76,40 +95,23 @@ module ReactOnRails
|
|
76
95
|
run "bundle"
|
77
96
|
end
|
78
97
|
|
79
|
-
def
|
80
|
-
|
81
|
-
|
82
|
-
package_json.manager.add(["react-on-rails@#{ReactOnRails::VERSION}"])
|
83
|
-
else
|
84
|
-
# otherwise add latest
|
85
|
-
puts "Adding the latest react-on-rails NPM module. Double check this is correct in package.json"
|
86
|
-
package_json.manager.add(["react-on-rails"])
|
87
|
-
end
|
98
|
+
def update_gitignore_for_generated_bundles
|
99
|
+
gitignore_path = File.join(destination_root, ".gitignore")
|
100
|
+
return unless File.exist?(gitignore_path)
|
88
101
|
|
89
|
-
|
90
|
-
package_json.manager.add([
|
91
|
-
"react",
|
92
|
-
"react-dom",
|
93
|
-
"@babel/preset-react",
|
94
|
-
"prop-types",
|
95
|
-
"babel-plugin-transform-react-remove-prop-types",
|
96
|
-
"babel-plugin-macros"
|
97
|
-
])
|
102
|
+
gitignore_content = File.read(gitignore_path)
|
98
103
|
|
99
|
-
|
104
|
+
additions = []
|
105
|
+
additions << "**/generated/**" unless gitignore_content.include?("**/generated/**")
|
106
|
+
additions << "ssr-generated" unless gitignore_content.include?("ssr-generated")
|
100
107
|
|
101
|
-
|
102
|
-
css-loader
|
103
|
-
css-minimizer-webpack-plugin
|
104
|
-
mini-css-extract-plugin
|
105
|
-
style-loader
|
106
|
-
])
|
108
|
+
return if additions.empty?
|
107
109
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
110
|
+
append_to_file ".gitignore" do
|
111
|
+
lines = ["\n# Generated React on Rails packs"]
|
112
|
+
lines.concat(additions)
|
113
|
+
"#{lines.join("\n")}\n"
|
114
|
+
end
|
113
115
|
end
|
114
116
|
|
115
117
|
def append_to_spec_rails_helper
|
@@ -118,25 +120,7 @@ module ReactOnRails
|
|
118
120
|
add_configure_rspec_to_compile_assets(rails_helper)
|
119
121
|
else
|
120
122
|
spec_helper = File.join(destination_root, "spec/spec_helper.rb")
|
121
|
-
if File.exist?(spec_helper)
|
122
|
-
add_configure_rspec_to_compile_assets(spec_helper)
|
123
|
-
else
|
124
|
-
# rubocop:disable Layout/EmptyLinesAroundArguments
|
125
|
-
GeneratorMessages.add_info(
|
126
|
-
<<-MSG.strip_heredoc
|
127
|
-
|
128
|
-
We did not find a spec/rails_helper.rb or spec/spec_helper.rb to add
|
129
|
-
the React on Rails Test helper, which ensures that if we are running
|
130
|
-
js tests, then we are using latest webpack assets. You can later add
|
131
|
-
this to your rspec config:
|
132
|
-
|
133
|
-
# This will use the defaults of :js and :server_rendering meta tags
|
134
|
-
ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
|
135
|
-
MSG
|
136
|
-
)
|
137
|
-
# rubocop:enable Layout/EmptyLinesAroundArguments
|
138
|
-
|
139
|
-
end
|
123
|
+
add_configure_rspec_to_compile_assets(spec_helper) if File.exist?(spec_helper)
|
140
124
|
end
|
141
125
|
end
|
142
126
|
|
@@ -145,10 +129,240 @@ module ReactOnRails
|
|
145
129
|
# Ensure that if we are running js tests, we are using latest webpack assets
|
146
130
|
# This will use the defaults of :js and :server_rendering meta tags
|
147
131
|
ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
|
132
|
+
end
|
148
133
|
STR
|
149
134
|
|
150
135
|
private
|
151
136
|
|
137
|
+
def setup_js_dependencies
|
138
|
+
add_js_dependencies
|
139
|
+
install_js_dependencies
|
140
|
+
end
|
141
|
+
|
142
|
+
def add_js_dependencies
|
143
|
+
add_react_on_rails_package
|
144
|
+
add_react_dependencies
|
145
|
+
add_css_dependencies
|
146
|
+
add_dev_dependencies
|
147
|
+
end
|
148
|
+
|
149
|
+
def add_react_on_rails_package
|
150
|
+
major_minor_patch_only = /\A\d+\.\d+\.\d+\z/
|
151
|
+
|
152
|
+
# Try to use package_json gem first, fall back to direct npm commands
|
153
|
+
react_on_rails_pkg = if ReactOnRails::VERSION.match?(major_minor_patch_only)
|
154
|
+
["react-on-rails@#{ReactOnRails::VERSION}"]
|
155
|
+
else
|
156
|
+
puts "Adding the latest react-on-rails NPM module. " \
|
157
|
+
"Double check this is correct in package.json"
|
158
|
+
["react-on-rails"]
|
159
|
+
end
|
160
|
+
|
161
|
+
puts "Installing React on Rails package..."
|
162
|
+
return if add_npm_dependencies(react_on_rails_pkg)
|
163
|
+
|
164
|
+
puts "Using direct npm commands as fallback"
|
165
|
+
success = system("npm", "install", *react_on_rails_pkg)
|
166
|
+
handle_npm_failure("react-on-rails package", react_on_rails_pkg) unless success
|
167
|
+
end
|
168
|
+
|
169
|
+
def add_react_dependencies
|
170
|
+
puts "Installing React dependencies..."
|
171
|
+
react_deps = %w[
|
172
|
+
react
|
173
|
+
react-dom
|
174
|
+
@babel/preset-react
|
175
|
+
prop-types
|
176
|
+
babel-plugin-transform-react-remove-prop-types
|
177
|
+
babel-plugin-macros
|
178
|
+
]
|
179
|
+
return if add_npm_dependencies(react_deps)
|
180
|
+
|
181
|
+
success = system("npm", "install", *react_deps)
|
182
|
+
handle_npm_failure("React dependencies", react_deps) unless success
|
183
|
+
end
|
184
|
+
|
185
|
+
def add_css_dependencies
|
186
|
+
puts "Installing CSS handling dependencies..."
|
187
|
+
css_deps = %w[
|
188
|
+
css-loader
|
189
|
+
css-minimizer-webpack-plugin
|
190
|
+
mini-css-extract-plugin
|
191
|
+
style-loader
|
192
|
+
]
|
193
|
+
return if add_npm_dependencies(css_deps)
|
194
|
+
|
195
|
+
success = system("npm", "install", *css_deps)
|
196
|
+
handle_npm_failure("CSS dependencies", css_deps) unless success
|
197
|
+
end
|
198
|
+
|
199
|
+
def add_dev_dependencies
|
200
|
+
puts "Installing development dependencies..."
|
201
|
+
dev_deps = %w[
|
202
|
+
@pmmmwh/react-refresh-webpack-plugin
|
203
|
+
react-refresh
|
204
|
+
]
|
205
|
+
return if add_npm_dependencies(dev_deps, dev: true)
|
206
|
+
|
207
|
+
success = system("npm", "install", "--save-dev", *dev_deps)
|
208
|
+
handle_npm_failure("development dependencies", dev_deps, dev: true) unless success
|
209
|
+
end
|
210
|
+
|
211
|
+
def install_js_dependencies
|
212
|
+
# Detect which package manager to use
|
213
|
+
success = if File.exist?(File.join(destination_root, "yarn.lock"))
|
214
|
+
system("yarn", "install")
|
215
|
+
elsif File.exist?(File.join(destination_root, "pnpm-lock.yaml"))
|
216
|
+
system("pnpm", "install")
|
217
|
+
elsif File.exist?(File.join(destination_root, "package-lock.json")) ||
|
218
|
+
File.exist?(File.join(destination_root, "package.json"))
|
219
|
+
# Use npm for package-lock.json or as default fallback
|
220
|
+
system("npm", "install")
|
221
|
+
else
|
222
|
+
true # No package manager detected, skip
|
223
|
+
end
|
224
|
+
|
225
|
+
unless success
|
226
|
+
GeneratorMessages.add_warning(<<~MSG.strip)
|
227
|
+
⚠️ JavaScript dependencies installation failed.
|
228
|
+
|
229
|
+
This could be due to network issues or missing package manager.
|
230
|
+
You can install dependencies manually later by running:
|
231
|
+
• npm install (if using npm)
|
232
|
+
• yarn install (if using yarn)
|
233
|
+
• pnpm install (if using pnpm)
|
234
|
+
MSG
|
235
|
+
end
|
236
|
+
|
237
|
+
success
|
238
|
+
end
|
239
|
+
|
240
|
+
def handle_npm_failure(dependency_type, packages, dev: false)
|
241
|
+
install_command = dev ? "npm install --save-dev" : "npm install"
|
242
|
+
GeneratorMessages.add_warning(<<~MSG.strip)
|
243
|
+
⚠️ Failed to install #{dependency_type}.
|
244
|
+
|
245
|
+
The following packages could not be installed automatically:
|
246
|
+
#{packages.map { |pkg| " • #{pkg}" }.join("\n")}
|
247
|
+
|
248
|
+
This could be due to network issues or missing package manager.
|
249
|
+
You can install them manually later by running:
|
250
|
+
#{install_command} #{packages.join(' ')}
|
251
|
+
MSG
|
252
|
+
end
|
253
|
+
|
254
|
+
def copy_webpack_main_config(base_path, config)
|
255
|
+
webpack_config_path = "config/webpack/webpack.config.js"
|
256
|
+
|
257
|
+
if File.exist?(webpack_config_path)
|
258
|
+
existing_content = File.read(webpack_config_path)
|
259
|
+
|
260
|
+
# Check if it's the standard Shakapacker config that we can safely replace
|
261
|
+
if standard_shakapacker_config?(existing_content)
|
262
|
+
# Remove the file first to avoid conflict prompt, then recreate it
|
263
|
+
remove_file(webpack_config_path, verbose: false)
|
264
|
+
# Show what we're doing
|
265
|
+
puts " #{set_color('replace', :green)} #{webpack_config_path} " \
|
266
|
+
"(auto-upgrading from standard Shakapacker to React on Rails config)"
|
267
|
+
template("#{base_path}/#{webpack_config_path}.tt", webpack_config_path, config)
|
268
|
+
elsif react_on_rails_config?(existing_content)
|
269
|
+
puts " #{set_color('identical', :blue)} #{webpack_config_path} " \
|
270
|
+
"(already React on Rails compatible)"
|
271
|
+
# Skip - don't need to do anything
|
272
|
+
else
|
273
|
+
handle_custom_webpack_config(base_path, config, webpack_config_path)
|
274
|
+
end
|
275
|
+
else
|
276
|
+
# File doesn't exist, create it
|
277
|
+
template("#{base_path}/#{webpack_config_path}.tt", webpack_config_path, config)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def handle_custom_webpack_config(base_path, config, webpack_config_path)
|
282
|
+
# Custom config - ask user
|
283
|
+
puts "\n#{set_color('NOTICE:', :yellow)} Your webpack.config.js appears to be customized."
|
284
|
+
puts "React on Rails needs to replace it with an environment-specific loader."
|
285
|
+
puts "Your current config will be backed up to webpack.config.js.backup"
|
286
|
+
|
287
|
+
if yes?("Replace webpack.config.js with React on Rails version? (Y/n)")
|
288
|
+
# Create backup
|
289
|
+
backup_path = "#{webpack_config_path}.backup"
|
290
|
+
if File.exist?(webpack_config_path)
|
291
|
+
FileUtils.cp(webpack_config_path, backup_path)
|
292
|
+
puts " #{set_color('create', :green)} #{backup_path} (backup of your custom config)"
|
293
|
+
end
|
294
|
+
|
295
|
+
template("#{base_path}/#{webpack_config_path}.tt", webpack_config_path, config)
|
296
|
+
else
|
297
|
+
puts " #{set_color('skip', :yellow)} #{webpack_config_path}"
|
298
|
+
puts " #{set_color('WARNING:', :red)} React on Rails may not work correctly " \
|
299
|
+
"without the environment-specific webpack config"
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
def standard_shakapacker_config?(content)
|
304
|
+
# Get the expected default config based on Shakapacker version
|
305
|
+
expected_configs = shakapacker_default_configs
|
306
|
+
|
307
|
+
# Check if the content matches any of the known default configurations
|
308
|
+
expected_configs.any? { |config| content_matches_template?(content, config) }
|
309
|
+
end
|
310
|
+
|
311
|
+
def content_matches_template?(content, template)
|
312
|
+
# Normalize whitespace and compare
|
313
|
+
normalize_config_content(content) == normalize_config_content(template)
|
314
|
+
end
|
315
|
+
|
316
|
+
def normalize_config_content(content)
|
317
|
+
# Remove comments, normalize whitespace, and clean up for comparison
|
318
|
+
content.gsub(%r{//.*$}, "") # Remove single-line comments
|
319
|
+
.gsub(%r{/\*.*?\*/}m, "") # Remove multi-line comments
|
320
|
+
.gsub(/\s+/, " ") # Normalize whitespace
|
321
|
+
.strip
|
322
|
+
end
|
323
|
+
|
324
|
+
def shakapacker_default_configs
|
325
|
+
configs = []
|
326
|
+
|
327
|
+
# Shakapacker v7+ (generateWebpackConfig function)
|
328
|
+
configs << <<~CONFIG
|
329
|
+
// See the shakacode/shakapacker README and docs directory for advice on customizing your webpackConfig.
|
330
|
+
const { generateWebpackConfig } = require('shakapacker')
|
331
|
+
|
332
|
+
const webpackConfig = generateWebpackConfig()
|
333
|
+
|
334
|
+
module.exports = webpackConfig
|
335
|
+
CONFIG
|
336
|
+
|
337
|
+
# Shakapacker v6 (webpackConfig object)
|
338
|
+
configs << <<~CONFIG
|
339
|
+
const { webpackConfig } = require('shakapacker')
|
340
|
+
|
341
|
+
// See the shakacode/shakapacker README and docs directory for advice on customizing your webpackConfig.
|
342
|
+
|
343
|
+
module.exports = webpackConfig
|
344
|
+
CONFIG
|
345
|
+
|
346
|
+
# Also check without comments for variations
|
347
|
+
configs << <<~CONFIG
|
348
|
+
const { generateWebpackConfig } = require('shakapacker')
|
349
|
+
const webpackConfig = generateWebpackConfig()
|
350
|
+
module.exports = webpackConfig
|
351
|
+
CONFIG
|
352
|
+
|
353
|
+
configs << <<~CONFIG
|
354
|
+
const { webpackConfig } = require('shakapacker')
|
355
|
+
module.exports = webpackConfig
|
356
|
+
CONFIG
|
357
|
+
|
358
|
+
configs
|
359
|
+
end
|
360
|
+
|
361
|
+
def react_on_rails_config?(content)
|
362
|
+
# Check if it already has React on Rails environment-specific loading
|
363
|
+
content.include?("envSpecificConfig") || content.include?("env.nodeEnv")
|
364
|
+
end
|
365
|
+
|
152
366
|
# From https://github.com/rails/rails/blob/4c940b2dbfb457f67c6250b720f63501d74a45fd/railties/lib/rails/generators/rails/app/app_generator.rb
|
153
367
|
def app_name
|
154
368
|
@app_name ||= (defined_app_const_base? ? defined_app_name : File.basename(destination_root))
|
@@ -1,11 +1,41 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "package_json"
|
4
3
|
require "rainbow"
|
4
|
+
require "json"
|
5
5
|
|
6
6
|
module GeneratorHelper
|
7
7
|
def package_json
|
8
|
+
# Lazy load package_json gem only when actually needed for dependency management
|
9
|
+
|
10
|
+
require "package_json" unless defined?(PackageJson)
|
8
11
|
@package_json ||= PackageJson.read
|
12
|
+
rescue LoadError
|
13
|
+
puts "Warning: package_json gem not available. This is expected before Shakapacker installation."
|
14
|
+
puts "Dependencies will be installed using the default package manager after Shakapacker setup."
|
15
|
+
nil
|
16
|
+
rescue StandardError => e
|
17
|
+
puts "Warning: Could not read package.json: #{e.message}"
|
18
|
+
puts "This is normal before Shakapacker creates the package.json file."
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
# Safe wrapper for package_json operations
|
23
|
+
def add_npm_dependencies(packages, dev: false)
|
24
|
+
pj = package_json
|
25
|
+
return false unless pj
|
26
|
+
|
27
|
+
begin
|
28
|
+
if dev
|
29
|
+
pj.manager.add(packages, type: :dev)
|
30
|
+
else
|
31
|
+
pj.manager.add(packages)
|
32
|
+
end
|
33
|
+
true
|
34
|
+
rescue StandardError => e
|
35
|
+
puts "Warning: Could not add packages via package_json gem: #{e.message}"
|
36
|
+
puts "Will fall back to direct npm commands."
|
37
|
+
false
|
38
|
+
end
|
9
39
|
end
|
10
40
|
|
11
41
|
# Takes a relative path from the destination root, such as `.gitignore` or `app/assets/javascripts/application.js`
|
@@ -61,4 +91,8 @@ module GeneratorHelper
|
|
61
91
|
def add_documentation_reference(message, source)
|
62
92
|
"#{message} \n#{source}"
|
63
93
|
end
|
94
|
+
|
95
|
+
def component_extension(options)
|
96
|
+
options.typescript? ? "tsx" : "jsx"
|
97
|
+
end
|
64
98
|
end
|
@@ -38,37 +38,158 @@ module GeneratorMessages
|
|
38
38
|
@output = []
|
39
39
|
end
|
40
40
|
|
41
|
-
def helpful_message_after_installation
|
41
|
+
def helpful_message_after_installation(component_name: "HelloWorld", route: "hello_world")
|
42
|
+
process_manager_section = build_process_manager_section
|
43
|
+
testing_section = build_testing_section
|
44
|
+
package_manager = detect_package_manager
|
45
|
+
shakapacker_status = build_shakapacker_status_section
|
46
|
+
|
42
47
|
<<~MSG
|
43
48
|
|
44
|
-
|
49
|
+
╔════════════════════════════════════════════════════════════════════════╗
|
50
|
+
║ 🎉 React on Rails Successfully Installed! ║
|
51
|
+
╚════════════════════════════════════════════════════════════════════════╝
|
52
|
+
#{process_manager_section}#{shakapacker_status}
|
53
|
+
|
54
|
+
📋 QUICK START:
|
55
|
+
─────────────────────────────────────────────────────────────────────────
|
56
|
+
1. Install dependencies:
|
57
|
+
#{Rainbow("bundle && #{package_manager} install").cyan}
|
58
|
+
|
59
|
+
2. Start the app:
|
60
|
+
./bin/dev # HMR (Hot Module Replacement) mode
|
61
|
+
./bin/dev static # Static bundles (no HMR, faster initial load)
|
62
|
+
./bin/dev prod # Production-like mode for testing
|
63
|
+
./bin/dev help # See all available options
|
64
|
+
|
65
|
+
3. Visit: #{Rainbow(route ? "http://localhost:3000/#{route}" : 'http://localhost:3000').cyan.underline}
|
66
|
+
✨ KEY FEATURES:
|
67
|
+
─────────────────────────────────────────────────────────────────────────
|
68
|
+
• Auto-registration enabled - Your layout only needs:
|
69
|
+
<%= javascript_pack_tag %>
|
70
|
+
<%= stylesheet_pack_tag %>
|
71
|
+
|
72
|
+
• Server-side rendering - Enabled with prerender option in app/views/hello_world/index.html.erb:
|
73
|
+
<%= react_component("#{component_name}", props: @hello_world_props, prerender: true) %>
|
74
|
+
|
75
|
+
📚 LEARN MORE:
|
76
|
+
─────────────────────────────────────────────────────────────────────────
|
77
|
+
• Documentation: #{Rainbow('https://www.shakacode.com/react-on-rails/docs/').cyan.underline}
|
78
|
+
• Webpack customization: #{Rainbow('https://github.com/shakacode/shakapacker#webpack-configuration').cyan.underline}
|
79
|
+
|
80
|
+
💡 TIP: Run 'bin/dev help' for development server options and troubleshooting#{testing_section}
|
81
|
+
MSG
|
82
|
+
end
|
45
83
|
|
46
|
-
|
47
|
-
|
84
|
+
private
|
85
|
+
|
86
|
+
def build_process_manager_section
|
87
|
+
process_manager = detect_process_manager
|
88
|
+
if process_manager
|
89
|
+
if process_manager == "overmind"
|
90
|
+
"\n📦 #{Rainbow("#{process_manager} detected ✓").green} " \
|
91
|
+
"#{Rainbow('(Recommended for easier debugging)').blue}"
|
92
|
+
else
|
93
|
+
"\n📦 #{Rainbow("#{process_manager} detected ✓").green}"
|
94
|
+
end
|
95
|
+
else
|
96
|
+
<<~INSTALL
|
97
|
+
|
98
|
+
⚠️ No process manager detected. Install one:
|
99
|
+
#{Rainbow('brew install overmind').yellow.bold} # Recommended (easier debugging)
|
100
|
+
#{Rainbow('gem install foreman').yellow} # Alternative
|
101
|
+
INSTALL
|
102
|
+
end
|
103
|
+
end
|
48
104
|
|
49
|
-
|
105
|
+
def build_testing_section
|
106
|
+
# Check if we have any spec files to determine if testing setup is needed
|
107
|
+
has_spec_files = File.exist?("spec/rails_helper.rb") || File.exist?("spec/spec_helper.rb")
|
50
108
|
|
51
|
-
|
109
|
+
return "" if has_spec_files
|
52
110
|
|
53
|
-
|
111
|
+
<<~TESTING
|
54
112
|
|
55
|
-
./bin/dev # Running with HMR
|
56
113
|
|
57
|
-
|
114
|
+
🧪 TESTING SETUP (Optional):
|
115
|
+
─────────────────────────────────────────────────────────────────────────
|
116
|
+
For JavaScript testing with asset compilation, add this to your RSpec config:
|
58
117
|
|
59
|
-
|
118
|
+
# In spec/rails_helper.rb or spec/spec_helper.rb:
|
119
|
+
ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
|
120
|
+
TESTING
|
121
|
+
end
|
60
122
|
|
61
|
-
|
62
|
-
|
123
|
+
def detect_process_manager
|
124
|
+
if system("which overmind > /dev/null 2>&1")
|
125
|
+
"overmind"
|
126
|
+
elsif system("which foreman > /dev/null 2>&1")
|
127
|
+
"foreman"
|
128
|
+
end
|
129
|
+
end
|
63
130
|
|
64
|
-
|
131
|
+
def build_shakapacker_status_section
|
132
|
+
version_warning = check_shakapacker_version_warning
|
133
|
+
|
134
|
+
if File.exist?(".shakapacker_just_installed")
|
135
|
+
base_message = <<~SHAKAPACKER
|
136
|
+
|
137
|
+
📦 SHAKAPACKER SETUP:
|
138
|
+
─────────────────────────────────────────────────────────────────────────
|
139
|
+
#{Rainbow('✓ Added to Gemfile automatically').green}
|
140
|
+
#{Rainbow('✓ Installer ran successfully').green}
|
141
|
+
#{Rainbow('✓ Webpack integration configured').green}
|
142
|
+
SHAKAPACKER
|
143
|
+
base_message + version_warning
|
144
|
+
elsif File.exist?("bin/shakapacker") && File.exist?("bin/shakapacker-dev-server")
|
145
|
+
"\n📦 #{Rainbow('Shakapacker already configured ✓').green}#{version_warning}"
|
146
|
+
else
|
147
|
+
"\n📦 #{Rainbow('Shakapacker setup may be incomplete').yellow}#{version_warning}"
|
148
|
+
end
|
149
|
+
end
|
65
150
|
|
66
|
-
|
151
|
+
def check_shakapacker_version_warning
|
152
|
+
# Try to detect Shakapacker version from Gemfile.lock
|
153
|
+
return "" unless File.exist?("Gemfile.lock")
|
67
154
|
|
68
|
-
|
155
|
+
gemfile_lock_content = File.read("Gemfile.lock")
|
156
|
+
shakapacker_match = gemfile_lock_content.match(/shakapacker \((\d+\.\d+\.\d+)\)/)
|
69
157
|
|
70
|
-
|
71
|
-
|
158
|
+
return "" unless shakapacker_match
|
159
|
+
|
160
|
+
version = shakapacker_match[1]
|
161
|
+
major_version = version.split(".").first.to_i
|
162
|
+
|
163
|
+
if major_version < 8
|
164
|
+
<<~WARNING
|
165
|
+
|
166
|
+
⚠️ #{Rainbow('IMPORTANT: Upgrade Recommended').yellow.bold}
|
167
|
+
─────────────────────────────────────────────────────────────────────────
|
168
|
+
You are using Shakapacker #{version}. React on Rails v15+ works best with
|
169
|
+
Shakapacker 8.0+ for optimal Hot Module Replacement and build performance.
|
170
|
+
|
171
|
+
To upgrade: #{Rainbow('bundle update shakapacker').cyan}
|
172
|
+
|
173
|
+
Learn more: #{Rainbow('https://github.com/shakacode/shakapacker').cyan.underline}
|
174
|
+
WARNING
|
175
|
+
else
|
176
|
+
""
|
177
|
+
end
|
178
|
+
rescue StandardError
|
179
|
+
# If version detection fails, don't show a warning to avoid noise
|
180
|
+
""
|
181
|
+
end
|
182
|
+
|
183
|
+
def detect_package_manager
|
184
|
+
# Check for lock files to determine package manager
|
185
|
+
if File.exist?("yarn.lock")
|
186
|
+
"yarn"
|
187
|
+
elsif File.exist?("pnpm-lock.yaml")
|
188
|
+
"pnpm"
|
189
|
+
else
|
190
|
+
# Default to npm (Shakapacker 8.x default) - covers package-lock.json and no lockfile
|
191
|
+
"npm"
|
192
|
+
end
|
72
193
|
end
|
73
194
|
end
|
74
195
|
end
|