Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions react_on_rails/lib/generators/react_on_rails/generator_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions react_on_rails/spec/dummy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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"
Expand Down
Loading