Skip to content

Commit 63505ab

Browse files
committed
refactor: Extract JS dependency management into shared module
This refactoring addresses duplicate code between BaseGenerator and InstallGenerator by extracting JavaScript dependency management into a shared JsDependencyManager module. Key improvements: - Eliminates ~270 lines of duplicated code - Centralizes dependency definitions in frozen constants - Removes Babel dependencies (now managed by Shakapacker) - Changes default transpiler to SWC in shakapacker.yml - Adds comprehensive test coverage for the new module - Simplifies dependency installation flow The module leverages package_json gem (always available via Shakapacker) for reliable cross-platform package management. Breaking change: Default webpack_loader changed from 'babel' to 'swc' in generated shakapacker.yml for better performance.
1 parent 83f2686 commit 63505ab

File tree

5 files changed

+430
-300
lines changed

5 files changed

+430
-300
lines changed

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)