Skip to content

Commit ae8b1b1

Browse files
justin808claude
andauthored
Refactor: Extract JS dependency management into shared module (#2051)
## Summary This PR refactors JavaScript dependency management in React on Rails generators by extracting duplicated code into a shared `JsDependencyManager` module. ## Changes - **Eliminates ~270 lines of duplicated code** between `BaseGenerator` and `InstallGenerator` - **Centralizes dependency definitions** in frozen constants with clear documentation - **Removes Babel dependencies** that are now managed by Shakapacker - **Changes default transpiler to SWC** in generated shakapacker.yml for better performance - **Adds comprehensive test coverage** for the new module - **Simplifies dependency installation flow** using package_json gem ## Key Improvements ### Code Organization - All JS dependencies are now defined as frozen constants (REACT_DEPENDENCIES, CSS_DEPENDENCIES, etc.) - Clear separation of concerns with well-documented module - Consistent error handling across all generators ### Dependency Management - Leverages package_json gem (always available via Shakapacker) for reliable cross-platform package management - Removes Babel-related packages that Shakapacker now handles automatically - Adds support for Rspack and TypeScript dependencies ### Default Configuration - ⚠️ **Breaking Change**: Default `webpack_loader` changed from `'babel'` to `'swc'` in generated shakapacker.yml - SWC provides better performance and has built-in TypeScript support - Users who need Babel can still configure it manually ## Testing New test file `spec/react_on_rails/generators/message_deduplication_spec.rb` verifies: - No duplicate post-install messages - Proper module inclusion in both generators - Correct dependency installation flow ## Migration Notes For new installations, this change is transparent. Existing projects are not affected as this only impacts the generator output for new setups. --- Supersedes and closes #2030 (that PR had merge conflict issues, so starting fresh with a clean branch) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Refactor** * Centralized JavaScript dependency management across generators for more consistent installation and simplified generator behavior. * **Configuration** * Installer config adds javascript_transpiler and assets_bundler options; previous webpack-specific key removed. * **Tests** * New specs for post-install messages, message deduplication, and JS dependency flows to validate installation messaging and dependency handling. * **Changelog** * Generator no longer auto-installs Babel presets; SWC is the default transpiler and Babel must be added manually. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 83f2686 commit ae8b1b1

File tree

8 files changed

+1000
-300
lines changed

8 files changed

+1000
-300
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ Changes since the last non-beta release.
5757

5858
- **Generator Configuration Modernization**: Updated the generator to enable recommended configurations by default for new applications. `config.build_test_command` is now uncommented and set to `"RAILS_ENV=test bin/shakapacker"` by default, enabling automatic asset building during tests for better integration test reliability. `config.auto_load_bundle = true` is now set by default, enabling automatic loading of component bundles. `config.components_subdirectory = "ror_components"` is now set by default, organizing React components in a dedicated subdirectory. **Note:** These changes only affect newly generated applications. Existing applications are unaffected and do not need to make any changes. If you want to adopt these settings in an existing app, you can manually add them to your `config/initializers/react_on_rails.rb` file. [PR 2039](https://github.com/shakacode/react_on_rails/pull/2039) by [justin808](https://github.com/justin808).
5959

60+
- **Removed Babel Dependency Installation**: The generator no longer installs `@babel/preset-react` or `@babel/preset-typescript` packages. Shakapacker handles JavaScript transpiler configuration (Babel, SWC, or esbuild) via the `javascript_transpiler` setting in `shakapacker.yml`. SWC is now the default transpiler and includes built-in support for React and TypeScript. Users who explicitly choose Babel will need to manually install and configure the required presets. This change reduces unnecessary dependencies and aligns with Shakapacker's modular transpiler approach. [PR 2051](https://github.com/shakacode/react_on_rails/pull/2051) by [justin808](https://github.com/justin808).
61+
6062
#### Documentation
6163

6264
- **Simplified Configuration Files**: Improved configuration documentation and generator template for better clarity and usability. Reduced generator template from 67 to 42 lines (37% reduction). Added comprehensive testing configuration guide. Reorganized configuration docs into Essential vs Advanced sections. Enhanced Doctor program with diagnostics for server rendering and test compilation consistency. [PR #2011](https://github.com/shakacode/react_on_rails/pull/2011) by [justin808](https://github.com/justin808).

lib/generators/react_on_rails/base_generator.rb

Lines changed: 3 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
require "fileutils"
55
require_relative "generator_messages"
66
require_relative "generator_helper"
7+
require_relative "js_dependency_manager"
78
module ReactOnRails
89
module Generators
910
class BaseGenerator < Rails::Generators::Base
1011
include GeneratorHelper
12+
include JsDependencyManager
1113

1214
Rails::Generators.hide_namespace(namespace)
1315
source_root(File.expand_path("templates", __dir__))
@@ -107,7 +109,7 @@ def add_base_gems_to_gemfile
107109
run "bundle"
108110
end
109111

110-
def update_gitignore_for_generated_bundles
112+
def update_gitignore_for_auto_registration
111113
gitignore_path = File.join(destination_root, ".gitignore")
112114
return unless File.exist?(gitignore_path)
113115

@@ -146,123 +148,6 @@ def append_to_spec_rails_helper
146148

147149
private
148150

149-
def setup_js_dependencies
150-
add_js_dependencies
151-
install_js_dependencies
152-
end
153-
154-
def add_js_dependencies
155-
add_react_on_rails_package
156-
add_react_dependencies
157-
add_css_dependencies
158-
add_dev_dependencies
159-
end
160-
161-
def add_react_on_rails_package
162-
major_minor_patch_only = /\A\d+\.\d+\.\d+\z/
163-
164-
# Try to use package_json gem first, fall back to direct npm commands
165-
react_on_rails_pkg = if ReactOnRails::VERSION.match?(major_minor_patch_only)
166-
["react-on-rails@#{ReactOnRails::VERSION}"]
167-
else
168-
puts "Adding the latest react-on-rails NPM module. " \
169-
"Double check this is correct in package.json"
170-
["react-on-rails"]
171-
end
172-
173-
puts "Installing React on Rails package..."
174-
return if add_npm_dependencies(react_on_rails_pkg)
175-
176-
puts "Using direct npm commands as fallback"
177-
success = system("npm", "install", *react_on_rails_pkg)
178-
handle_npm_failure("react-on-rails package", react_on_rails_pkg) unless success
179-
end
180-
181-
def add_react_dependencies
182-
puts "Installing React dependencies..."
183-
react_deps = %w[
184-
react
185-
react-dom
186-
@babel/preset-react
187-
prop-types
188-
babel-plugin-transform-react-remove-prop-types
189-
babel-plugin-macros
190-
]
191-
return if add_npm_dependencies(react_deps)
192-
193-
success = system("npm", "install", *react_deps)
194-
handle_npm_failure("React dependencies", react_deps) unless success
195-
end
196-
197-
def add_css_dependencies
198-
puts "Installing CSS handling dependencies..."
199-
css_deps = %w[
200-
css-loader
201-
css-minimizer-webpack-plugin
202-
mini-css-extract-plugin
203-
style-loader
204-
]
205-
return if add_npm_dependencies(css_deps)
206-
207-
success = system("npm", "install", *css_deps)
208-
handle_npm_failure("CSS dependencies", css_deps) unless success
209-
end
210-
211-
def add_dev_dependencies
212-
puts "Installing development dependencies..."
213-
dev_deps = %w[
214-
@pmmmwh/react-refresh-webpack-plugin
215-
react-refresh
216-
]
217-
return if add_npm_dependencies(dev_deps, dev: true)
218-
219-
success = system("npm", "install", "--save-dev", *dev_deps)
220-
handle_npm_failure("development dependencies", dev_deps, dev: true) unless success
221-
end
222-
223-
def install_js_dependencies
224-
# Detect which package manager to use
225-
success = if File.exist?(File.join(destination_root, "yarn.lock"))
226-
system("yarn", "install")
227-
elsif File.exist?(File.join(destination_root, "pnpm-lock.yaml"))
228-
system("pnpm", "install")
229-
elsif File.exist?(File.join(destination_root, "package-lock.json")) ||
230-
File.exist?(File.join(destination_root, "package.json"))
231-
# Use npm for package-lock.json or as default fallback
232-
system("npm", "install")
233-
else
234-
true # No package manager detected, skip
235-
end
236-
237-
unless success
238-
GeneratorMessages.add_warning(<<~MSG.strip)
239-
⚠️ JavaScript dependencies installation failed.
240-
241-
This could be due to network issues or missing package manager.
242-
You can install dependencies manually later by running:
243-
• npm install (if using npm)
244-
• yarn install (if using yarn)
245-
• pnpm install (if using pnpm)
246-
MSG
247-
end
248-
249-
success
250-
end
251-
252-
def handle_npm_failure(dependency_type, packages, dev: false)
253-
install_command = dev ? "npm install --save-dev" : "npm install"
254-
GeneratorMessages.add_warning(<<~MSG.strip)
255-
⚠️ Failed to install #{dependency_type}.
256-
257-
The following packages could not be installed automatically:
258-
#{packages.map { |pkg| " • #{pkg}" }.join("\n")}
259-
260-
This could be due to network issues or missing package manager.
261-
You can install them manually later by running:
262-
#{install_command} #{packages.join(' ')}
263-
MSG
264-
end
265-
266151
def copy_webpack_main_config(base_path, config)
267152
webpack_config_path = "config/webpack/webpack.config.js"
268153

lib/generators/react_on_rails/install_generator.rb

Lines changed: 5 additions & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
require "json"
55
require_relative "generator_helper"
66
require_relative "generator_messages"
7+
require_relative "js_dependency_manager"
78

89
module ReactOnRails
910
module Generators
1011
# rubocop:disable Metrics/ClassLength
1112
class InstallGenerator < Rails::Generators::Base
1213
include GeneratorHelper
14+
include JsDependencyManager
1315

1416
# fetch USAGE file for details generator description
1517
source_root(File.expand_path(__dir__))
@@ -113,10 +115,7 @@ def invoke_generators
113115
end
114116

115117
def setup_react_dependencies
116-
@added_dependencies_to_package_json ||= false
117-
@ran_direct_installs ||= false
118-
add_js_dependencies
119-
install_js_dependencies if @added_dependencies_to_package_json && !@ran_direct_installs
118+
setup_js_dependencies
120119
end
121120

122121
# NOTE: other requirements for existing files such as .gitignore or application.
@@ -366,29 +365,8 @@ def missing_package_manager?
366365

367366
def install_typescript_dependencies
368367
puts Rainbow("📝 Installing TypeScript dependencies...").yellow
369-
370-
# Install TypeScript and React type definitions
371-
typescript_packages = %w[
372-
typescript
373-
@types/react
374-
@types/react-dom
375-
@babel/preset-typescript
376-
]
377-
378-
# Try using GeneratorHelper first (package manager agnostic)
379-
return if add_npm_dependencies(typescript_packages, dev: true)
380-
381-
# Fallback to npm if GeneratorHelper fails
382-
success = system("npm", "install", "--save-dev", *typescript_packages)
383-
return if success
384-
385-
warning = <<~MSG.strip
386-
⚠️ Failed to install TypeScript dependencies automatically.
387-
388-
Please run manually:
389-
npm install --save-dev #{typescript_packages.join(' ')}
390-
MSG
391-
GeneratorMessages.add_warning(warning)
368+
# Delegate to shared module for consistent dependency management
369+
add_typescript_dependencies
392370
end
393371

394372
def create_css_module_types
@@ -450,159 +428,6 @@ def create_typescript_config
450428
puts Rainbow("✅ Created tsconfig.json").green
451429
end
452430

453-
def add_js_dependencies
454-
add_react_on_rails_package
455-
add_react_dependencies
456-
add_css_dependencies
457-
add_rspack_dependencies if options.rspack?
458-
add_dev_dependencies
459-
end
460-
461-
def add_react_on_rails_package
462-
major_minor_patch_only = /\A\d+\.\d+\.\d+\z/
463-
464-
# Try to use package_json gem first, fall back to direct npm commands
465-
react_on_rails_pkg = if ReactOnRails::VERSION.match?(major_minor_patch_only)
466-
["react-on-rails@#{ReactOnRails::VERSION}"]
467-
else
468-
puts "Adding the latest react-on-rails NPM module. " \
469-
"Double check this is correct in package.json"
470-
["react-on-rails"]
471-
end
472-
473-
puts "Installing React on Rails package..."
474-
if add_npm_dependencies(react_on_rails_pkg)
475-
@added_dependencies_to_package_json = true
476-
return
477-
end
478-
479-
puts "Using direct npm commands as fallback"
480-
success = system("npm", "install", *react_on_rails_pkg)
481-
@ran_direct_installs = true if success
482-
handle_npm_failure("react-on-rails package", react_on_rails_pkg) unless success
483-
end
484-
485-
def add_react_dependencies
486-
puts "Installing React dependencies..."
487-
react_deps = %w[
488-
react
489-
react-dom
490-
@babel/preset-react
491-
prop-types
492-
babel-plugin-transform-react-remove-prop-types
493-
babel-plugin-macros
494-
]
495-
if add_npm_dependencies(react_deps)
496-
@added_dependencies_to_package_json = true
497-
return
498-
end
499-
500-
success = system("npm", "install", *react_deps)
501-
@ran_direct_installs = true if success
502-
handle_npm_failure("React dependencies", react_deps) unless success
503-
end
504-
505-
def add_css_dependencies
506-
puts "Installing CSS handling dependencies..."
507-
css_deps = %w[
508-
css-loader
509-
css-minimizer-webpack-plugin
510-
mini-css-extract-plugin
511-
style-loader
512-
]
513-
if add_npm_dependencies(css_deps)
514-
@added_dependencies_to_package_json = true
515-
return
516-
end
517-
518-
success = system("npm", "install", *css_deps)
519-
@ran_direct_installs = true if success
520-
handle_npm_failure("CSS dependencies", css_deps) unless success
521-
end
522-
523-
def add_rspack_dependencies
524-
puts "Installing Rspack core dependencies..."
525-
rspack_deps = %w[
526-
@rspack/core
527-
rspack-manifest-plugin
528-
]
529-
if add_npm_dependencies(rspack_deps)
530-
@added_dependencies_to_package_json = true
531-
return
532-
end
533-
534-
success = system("npm", "install", *rspack_deps)
535-
@ran_direct_installs = true if success
536-
handle_npm_failure("Rspack dependencies", rspack_deps) unless success
537-
end
538-
539-
def add_dev_dependencies
540-
puts "Installing development dependencies..."
541-
dev_deps = if options.rspack?
542-
%w[
543-
@rspack/cli
544-
@rspack/plugin-react-refresh
545-
react-refresh
546-
]
547-
else
548-
%w[
549-
@pmmmwh/react-refresh-webpack-plugin
550-
react-refresh
551-
]
552-
end
553-
if add_npm_dependencies(dev_deps, dev: true)
554-
@added_dependencies_to_package_json = true
555-
return
556-
end
557-
558-
success = system("npm", "install", "--save-dev", *dev_deps)
559-
@ran_direct_installs = true if success
560-
handle_npm_failure("development dependencies", dev_deps, dev: true) unless success
561-
end
562-
563-
def install_js_dependencies
564-
# Detect which package manager to use
565-
success = if File.exist?(File.join(destination_root, "yarn.lock"))
566-
system("yarn", "install")
567-
elsif File.exist?(File.join(destination_root, "pnpm-lock.yaml"))
568-
system("pnpm", "install")
569-
elsif File.exist?(File.join(destination_root, "package-lock.json")) ||
570-
File.exist?(File.join(destination_root, "package.json"))
571-
# Use npm for package-lock.json or as default fallback
572-
system("npm", "install")
573-
else
574-
true # No package manager detected, skip
575-
end
576-
577-
unless success
578-
GeneratorMessages.add_warning(<<~MSG.strip)
579-
⚠️ JavaScript dependencies installation failed.
580-
581-
This could be due to network issues or missing package manager.
582-
You can install dependencies manually later by running:
583-
• npm install (if using npm)
584-
• yarn install (if using yarn)
585-
• pnpm install (if using pnpm)
586-
MSG
587-
end
588-
589-
success
590-
end
591-
592-
def handle_npm_failure(dependency_type, packages, dev: false)
593-
install_command = dev ? "npm install --save-dev" : "npm install"
594-
GeneratorMessages.add_warning(<<~MSG.strip)
595-
⚠️ Failed to install #{dependency_type}.
596-
597-
The following packages could not be installed automatically:
598-
#{packages.map { |pkg| " • #{pkg}" }.join("\n")}
599-
600-
This could be due to network issues or missing package manager.
601-
You can install them manually later by running:
602-
#{install_command} #{packages.join(' ')}
603-
MSG
604-
end
605-
606431
# Removed: Shakapacker auto-installation logic (now explicit dependency)
607432

608433
# Removed: Shakapacker 8+ is now required as explicit dependency

0 commit comments

Comments
 (0)