From c9cfc20fabd96b3a744afb40a1a19ef7257c3eea Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Tue, 25 Nov 2025 21:14:05 -1000 Subject: [PATCH 1/2] Improve SWC compiler detection and default dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add using_swc? helper method to detect SWC configuration: - Parses shakapacker.yml for javascript_transpiler setting - Returns true when SWC is explicitly configured - Returns true for Shakapacker 9.3.0+ when not specified (SWC default) - Returns true for fresh installations (SWC is recommended) Add SWC dependency installation to generator: - Add SWC_DEPENDENCIES constant (@swc/core, swc-loader) - Install SWC deps automatically when using_swc? returns true - Add to devDependencies since they're build-time tools Update spec/dummy to include SWC packages: - Add @swc/core and swc-loader to devDependencies - Matches the javascript_transpiler: swc setting in shakapacker.yml Add comprehensive tests for using_swc? helper: - Test explicit swc config returns true - Test explicit babel config returns false - Test missing config defaults to SWC for 9.3.0+ - Test missing file defaults to SWC - Test invalid YAML defaults to SWC Closes #1956 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../react_on_rails/generator_helper.rb | 60 +++++++++++++++ .../react_on_rails/js_dependency_manager.rb | 29 ++++++++ react_on_rails/spec/dummy/package.json | 2 + .../generators/generator_helper_spec.rb | 74 +++++++++++++++++++ 4 files changed, 165 insertions(+) diff --git a/react_on_rails/lib/generators/react_on_rails/generator_helper.rb b/react_on_rails/lib/generators/react_on_rails/generator_helper.rb index bcb9bf6e57..4ba82227e9 100644 --- a/react_on_rails/lib/generators/react_on_rails/generator_helper.rb +++ b/react_on_rails/lib/generators/react_on_rails/generator_helper.rb @@ -124,4 +124,64 @@ def shakapacker_version_9_or_higher? true end end + + # Check if SWC is configured as the JavaScript transpiler in shakapacker.yml + # + # @return [Boolean] true if SWC is configured or should be used by default + # + # Detection logic: + # 1. If shakapacker.yml exists and specifies javascript_transpiler: parse it + # 2. For Shakapacker 9.3.0+, SWC is the default if not specified + # 3. Returns true for fresh installations (SWC is recommended default) + # + # @note This method is used to determine whether to install SWC dependencies + # (@swc/core, swc-loader) instead of Babel dependencies during generation. + def using_swc? + return @using_swc if defined?(@using_swc) + + @using_swc = detect_swc_configuration + end + + private + + def detect_swc_configuration + shakapacker_yml_path = File.join(destination_root, "config/shakapacker.yml") + + if File.exist?(shakapacker_yml_path) + config = parse_shakapacker_yml(shakapacker_yml_path) + transpiler = config.dig("default", "javascript_transpiler") + + # Explicit configuration takes precedence + return transpiler == "swc" if transpiler + + # For Shakapacker 9.3.0+, SWC is the default + return shakapacker_version_9_3_or_higher? + end + + # Fresh install: SWC is recommended default for Shakapacker 9.3.0+ + shakapacker_version_9_3_or_higher? + end + + def parse_shakapacker_yml(path) + require "yaml" + # Support both old and new Psych versions + YAML.load_file(path, aliases: true) + rescue ArgumentError + # Older Psych versions don't support the aliases parameter + YAML.load_file(path) + rescue StandardError + # If we can't parse the file, return empty config + {} + end + + # Check if Shakapacker 9.3.0 or higher is available + # This version made SWC the default JavaScript transpiler + def shakapacker_version_9_3_or_higher? + return true unless defined?(ReactOnRails::PackerUtils) + + ReactOnRails::PackerUtils.shakapacker_version_requirement_met?("9.3.0") + rescue StandardError + # If we can't determine version, assume latest (which uses SWC) + true + end end diff --git a/react_on_rails/lib/generators/react_on_rails/js_dependency_manager.rb b/react_on_rails/lib/generators/react_on_rails/js_dependency_manager.rb index 8f5f5ade1e..81dc21ebe0 100644 --- a/react_on_rails/lib/generators/react_on_rails/js_dependency_manager.rb +++ b/react_on_rails/lib/generators/react_on_rails/js_dependency_manager.rb @@ -99,6 +99,13 @@ module JsDependencyManager @types/react-dom ].freeze + # SWC transpiler dependencies (for Shakapacker 9.3.0+ default transpiler) + # SWC is ~20x faster than Babel and is the default for new Shakapacker installations + SWC_DEPENDENCIES = %w[ + @swc/core + swc-loader + ].freeze + private def setup_js_dependencies @@ -118,6 +125,8 @@ def add_js_dependencies add_css_dependencies # Rspack dependencies are only added when --rspack flag is used add_rspack_dependencies if respond_to?(:options) && options&.rspack? + # SWC dependencies are only added when SWC is the configured transpiler + add_swc_dependencies if using_swc? # Dev dependencies vary based on bundler choice add_dev_dependencies end @@ -232,6 +241,26 @@ def add_rspack_dependencies MSG end + def add_swc_dependencies + puts "Installing SWC transpiler dependencies (20x faster than Babel)..." + return if add_packages(SWC_DEPENDENCIES, dev: true) + + GeneratorMessages.add_warning(<<~MSG.strip) + ⚠️ Failed to add SWC dependencies. + + SWC is the default JavaScript transpiler for Shakapacker 9.3.0+. + You can install them manually by running: + npm install --save-dev #{SWC_DEPENDENCIES.join(' ')} + MSG + rescue StandardError => e + GeneratorMessages.add_warning(<<~MSG.strip) + ⚠️ Error adding SWC dependencies: #{e.message} + + You can install them manually by running: + npm install --save-dev #{SWC_DEPENDENCIES.join(' ')} + MSG + end + def add_typescript_dependencies puts "Installing TypeScript dependencies..." return if add_packages(TYPESCRIPT_DEPENDENCIES, dev: true) diff --git a/react_on_rails/spec/dummy/package.json b/react_on_rails/spec/dummy/package.json index e9998a52db..5920cb3832 100644 --- a/react_on_rails/spec/dummy/package.json +++ b/react_on_rails/spec/dummy/package.json @@ -36,6 +36,7 @@ "@playwright/test": "^1.55.1", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.1", "@rescript/react": "^0.13.0", + "@swc/core": "^1.7.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "@types/react-helmet": "^6.1.5", @@ -55,6 +56,7 @@ "sass-resources-loader": "^2.1.0", "shakapacker": "9.4.0", "style-loader": "^3.3.1", + "swc-loader": "^0.2.6", "terser-webpack-plugin": "5.3.1", "url-loader": "^4.0.0", "webpack": "5.72.0", diff --git a/react_on_rails/spec/react_on_rails/generators/generator_helper_spec.rb b/react_on_rails/spec/react_on_rails/generators/generator_helper_spec.rb index eb8b447549..79e3328d14 100644 --- a/react_on_rails/spec/react_on_rails/generators/generator_helper_spec.rb +++ b/react_on_rails/spec/react_on_rails/generators/generator_helper_spec.rb @@ -109,4 +109,78 @@ def self.read end end end + + describe "#using_swc?" do + let(:shakapacker_yml_path) { File.join(destination_root, "config/shakapacker.yml") } + + before do + # Clear memoized value before each test + remove_instance_variable(:@using_swc) if instance_variable_defined?(:@using_swc) + FileUtils.mkdir_p(File.join(destination_root, "config")) + end + + after do + FileUtils.rm_rf(File.join(destination_root, "config")) + end + + context "when shakapacker.yml exists with javascript_transpiler: swc" do + before do + File.write(shakapacker_yml_path, <<~YAML) + default: &default + javascript_transpiler: swc + YAML + end + + it "returns true" do + expect(using_swc?).to be true + end + end + + context "when shakapacker.yml exists with javascript_transpiler: babel" do + before do + File.write(shakapacker_yml_path, <<~YAML) + default: &default + javascript_transpiler: babel + YAML + end + + it "returns false" do + expect(using_swc?).to be false + end + end + + context "when shakapacker.yml exists without javascript_transpiler setting" do + before do + File.write(shakapacker_yml_path, <<~YAML) + default: &default + source_path: app/javascript + YAML + end + + it "returns true for Shakapacker 9.3.0+ (SWC is default)" do + # The method assumes latest Shakapacker when version detection fails + expect(using_swc?).to be true + end + end + + context "when shakapacker.yml does not exist" do + before do + FileUtils.rm_f(shakapacker_yml_path) + end + + it "returns true for fresh installations (SWC is recommended)" do + expect(using_swc?).to be true + end + end + + context "when shakapacker.yml has parse errors" do + before do + File.write(shakapacker_yml_path, "invalid: yaml: [}") + end + + it "returns true (assumes latest Shakapacker with SWC default)" do + expect(using_swc?).to be true + end + end + end end From 7be99ea7c70dfe770ae5d5324015e85e1137af31 Mon Sep 17 00:00:00 2001 From: Justin Gordon Date: Tue, 25 Nov 2025 21:48:42 -1000 Subject: [PATCH 2/2] Fix CI: Add using_swc? stub to JsDependencyManager spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The JsDependencyManager tests create a mock test class that includes the module. After adding the using_swc? call to add_js_dependencies, the test class needs to provide this method (which comes from GeneratorHelper in actual generator classes). Changes: - Add using_swc? method stub to test class (defaults to true) - Add using_swc setter for test control - Add test for SWC_DEPENDENCIES constant 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../generators/js_dependency_manager_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/react_on_rails/spec/react_on_rails/generators/js_dependency_manager_spec.rb b/react_on_rails/spec/react_on_rails/generators/js_dependency_manager_spec.rb index 0c087b8b0c..08c0b7ea38 100644 --- a/react_on_rails/spec/react_on_rails/generators/js_dependency_manager_spec.rb +++ b/react_on_rails/spec/react_on_rails/generators/js_dependency_manager_spec.rb @@ -26,6 +26,13 @@ def destination_root "/test/path" end + # Mock using_swc? from GeneratorHelper (defaults to true for SWC testing) + def using_swc? + @using_swc.nil? ? true : @using_swc + end + + attr_writer :using_swc + # Test helpers attr_writer :add_npm_dependencies_result @@ -109,6 +116,12 @@ def errors ]) end + it "defines SWC_DEPENDENCIES" do + expect(ReactOnRails::Generators::JsDependencyManager::SWC_DEPENDENCIES).to( + eq(%w[@swc/core swc-loader]) + ) + end + it "does not include Babel presets in REACT_DEPENDENCIES" do expect(ReactOnRails::Generators::JsDependencyManager::REACT_DEPENDENCIES).not_to include( "@babel/preset-react"