Skip to content
Merged
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
8 changes: 6 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: yarn
# Disable cache for Node 22 due to V8 bug in 22.21.0
# https://github.com/nodejs/node/issues/56010
cache: ${{ matrix.node-version != '22' && 'yarn' || '' }}
cache-dependency-path: '**/yarn.lock'
- name: Print system information
run: |
Expand Down Expand Up @@ -123,7 +125,9 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: yarn
# Disable cache for Node 22 due to V8 bug in 22.21.0
# https://github.com/nodejs/node/issues/56010
cache: ${{ matrix.node-version != '22' && 'yarn' || '' }}
cache-dependency-path: '**/yarn.lock'
- name: Print system information
run: |
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ Changes since the last non-beta release.

- **Improved RSC Payload Error Handling**: Errors that happen during generation of RSC payload are transferred properly to rails side and logs the error message and stack. [PR #1888](https://github.com/shakacode/react_on_rails/pull/1888) by [AbanoubGhadban](https://github.com/AbanoubGhadban).

#### Changed

- **Shakapacker 9.0.0 Upgrade**: Upgraded Shakapacker from 8.2.0 to 9.0.0 with Babel transpiler configuration for compatibility. Key changes include:
- Configured `javascript_transpiler: babel` in shakapacker.yml (Shakapacker 9.0 defaults to SWC which has PropTypes handling issues)
- Added precompile hook support via `bin/shakapacker-precompile-hook` for ReScript builds and pack generation
- Configured CSS Modules to use default exports (`namedExport: false`) for backward compatibility with existing `import styles from` syntax
- Fixed webpack configuration to process SCSS rules and CSS loaders in a single pass for better performance
[PR 1904](https://github.com/shakacode/react_on_rails/pull/1904) by [justin808](https://github.com/justin808).

#### Bug Fixes

- **Use as Git dependency**: All packages can now be installed as Git dependencies. This is useful for development and testing purposes. See [CONTRIBUTING.md](./CONTRIBUTING.md#git-dependencies) for documentation. [PR #1873](https://github.com/shakacode/react_on_rails/pull/1873) by [alexeyr-ci2](https://github.com/alexeyr-ci2).
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.development_dependencies
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

gem "shakapacker", "8.2.0"
gem "shakapacker", "9.0.0"
gem "bootsnap", require: false
gem "rails", "~> 7.1"

Expand Down
4 changes: 2 additions & 2 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ GEM
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
semantic_range (3.1.0)
shakapacker (8.2.0)
shakapacker (9.0.0)
activesupport (>= 5.2)
package_json
rack-proxy (>= 0.6.1)
Expand Down Expand Up @@ -440,7 +440,7 @@ DEPENDENCIES
scss_lint
sdoc
selenium-webdriver (= 4.9.0)
shakapacker (= 8.2.0)
shakapacker (= 9.0.0)
spring (~> 4.0)
sprockets (~> 4.0)
sqlite3 (~> 1.6)
Expand Down
10 changes: 5 additions & 5 deletions knip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const config: KnipConfig = {
ignoreBinaries: [
// Has to be installed globally
'yalc',
// Used in package.json scripts (devDependency, so unlisted in production mode)
'nps',
// Pro package binaries used in Pro workflows
'playwright',
Expand Down Expand Up @@ -109,8 +110,9 @@ const config: KnipConfig = {
'bin/.*',
],
ignoreDependencies: [
// Knip thinks it can be a devDependency, but it's supposed to be in dependencies.
// Build-time dependencies not detected by Knip in any mode
'@babel/runtime',
'mini-css-extract-plugin',
// There's no ReScript plugin for Knip
'@rescript/react',
// The Babel plugin fails to detect it
Expand All @@ -120,17 +122,15 @@ const config: KnipConfig = {
'node-libs-browser',
// The below dependencies are not detected by the Webpack plugin
// due to the config issue.
'css-loader',
'expose-loader',
'file-loader',
'imports-loader',
'mini-css-extract-plugin',
'null-loader',
'sass',
'sass-loader',
'sass-resources-loader',
'style-loader',
'url-loader',
// Transitive dependency of shakapacker but listed as direct dependency
'webpack-merge',
],
},
},
Expand Down
4 changes: 2 additions & 2 deletions spec/dummy/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ GEM
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
semantic_range (3.1.0)
shakapacker (8.2.0)
shakapacker (9.0.0)
activesupport (>= 5.2)
package_json
rack-proxy (>= 0.6.1)
Expand Down Expand Up @@ -441,7 +441,7 @@ DEPENDENCIES
scss_lint
sdoc
selenium-webdriver (= 4.9.0)
shakapacker (= 8.2.0)
shakapacker (= 9.0.0)
spring (~> 4.0)
sprockets (~> 4.0)
sqlite3 (~> 1.6)
Expand Down
3 changes: 2 additions & 1 deletion spec/dummy/babel.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const defaultConfigFunc = require('shakapacker/package/babel/preset');
// eslint-disable-next-line import/extensions
const defaultConfigFunc = require('shakapacker/package/babel/preset.js');

module.exports = function createBabelConfig(api) {
const resultConfig = defaultConfigFunc(api);
Expand Down
101 changes: 101 additions & 0 deletions spec/dummy/bin/shakapacker-precompile-hook
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

# Shakapacker precompile hook
# This script runs before Shakapacker compilation in both development and production.
# See: https://github.com/shakacode/shakapacker/blob/main/docs/precompile_hook.md

require "fileutils"

# Find Rails root by walking upward looking for config/environment.rb
def find_rails_root
dir = Dir.pwd
loop do
return dir if File.exist?(File.join(dir, "config", "environment.rb"))

parent = File.dirname(dir)
return nil if parent == dir # Reached filesystem root

dir = parent
end
end

# Build ReScript if needed
def build_rescript_if_needed
# Check for both old (bsconfig.json) and new (rescript.json) config files
return unless File.exist?("bsconfig.json") || File.exist?("rescript.json")

puts "🔧 Building ReScript..."

# Cross-platform package manager detection
yarn_available = system("yarn", "--version", out: File::NULL, err: File::NULL)
npm_available = system("npm", "--version", out: File::NULL, err: File::NULL)

success = if yarn_available
system("yarn", "build:rescript")
elsif npm_available
system("npm", "run", "build:rescript")
else
warn "⚠️ Warning: Neither yarn nor npm found. Skipping ReScript build."
return
end

if success
puts "✅ ReScript build completed successfully"
else
warn "❌ ReScript build failed"
exit 1
end
end

# Generate React on Rails packs if needed
# rubocop:disable Metrics/CyclomaticComplexity
def generate_packs_if_needed
# Find Rails root directory
rails_root = find_rails_root
return unless rails_root

# Check if React on Rails initializer exists
initializer_path = File.join(rails_root, "config", "initializers", "react_on_rails.rb")
return unless File.exist?(initializer_path)

# Check if auto-pack generation is configured (match actual config assignments, not comments)
config_file = File.read(initializer_path)
# Match uncommented configuration lines only (lines not starting with #)
has_auto_load = config_file =~ /^\s*(?!#).*config\.auto_load_bundle\s*=/
has_components_subdir = config_file =~ /^\s*(?!#).*config\.components_subdirectory\s*=/
return unless has_auto_load || has_components_subdir

puts "📦 Generating React on Rails packs..."

# Cross-platform bundle availability check
bundle_available = system("bundle", "--version", out: File::NULL, err: File::NULL)
return unless bundle_available

# Check if rake task exists (use array form for security)
task_list = IO.popen(["bundle", "exec", "rails", "-T"], err: [:child, :out], &:read)
return unless task_list.include?("react_on_rails:generate_packs")

# Use array form for better cross-platform support
success = system("bundle", "exec", "rails", "react_on_rails:generate_packs")

if success
puts "✅ Pack generation completed successfully"
else
warn "❌ Pack generation failed"
exit 1
end
end
# rubocop:enable Metrics/CyclomaticComplexity

# Main execution
begin
build_rescript_if_needed
generate_packs_if_needed

exit 0
rescue StandardError => e
warn "❌ Precompile hook failed: #{e.message}"
warn e.backtrace.join("\n")
exit 1
end
8 changes: 8 additions & 0 deletions spec/dummy/config/shakapacker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ default: &default
source_entry_path: packs
public_root_path: public

# Use Babel instead of SWC (Shakapacker 9.0 default) for better compatibility
# SWC has issues with PropTypes handling
javascript_transpiler: babel

# Hook to run before compilation (e.g., for ReScript builds, pack generation)
# See: https://github.com/shakacode/shakapacker/blob/main/docs/precompile_hook.md
precompile_hook: bin/shakapacker-precompile-hook

cache_path: tmp/cache/shakapacker
webpack_compile_output: false
ensure_consistent_versioning: true
Expand Down
35 changes: 31 additions & 4 deletions spec/dummy/config/webpack/commonWebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,37 @@ const sassLoaderConfig = {
},
};

const scssConfigIndex = baseClientWebpackConfig.module.rules.findIndex((config) =>
'.scss'.match(config.test),
);
baseClientWebpackConfig.module.rules[scssConfigIndex]?.use.push(sassLoaderConfig);
// Process webpack rules in single pass for efficiency
baseClientWebpackConfig.module.rules.forEach((rule) => {
if (Array.isArray(rule.use)) {
// Add sass-resources-loader to all SCSS rules (both .scss and .module.scss)
if (rule.test && '.scss'.match(rule.test)) {
rule.use.push(sassLoaderConfig);
}

// Configure CSS Modules to use default exports (Shakapacker 9.0 compatibility)
// Shakapacker 9.0 defaults to namedExport: true, but we use default imports
// To restore backward compatibility with existing code using `import styles from`
rule.use.forEach((loader) => {
if (
loader &&
typeof loader === 'object' &&
loader.loader &&
typeof loader.loader === 'string' &&
loader.loader.includes('css-loader') &&
loader.options &&
typeof loader.options === 'object' &&
loader.options.modules &&
typeof loader.options.modules === 'object'
) {
// eslint-disable-next-line no-param-reassign
loader.options.modules.namedExport = false;
// eslint-disable-next-line no-param-reassign
loader.options.modules.exportLocalsConvention = 'camelCase';
}
});
}
});

// add jquery
const exposeJQuery = {
Expand Down
2 changes: 1 addition & 1 deletion spec/dummy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
"sass": "^1.43.4",
"sass-loader": "^12.3.0",
"sass-resources-loader": "^2.1.0",
"shakapacker": "8.2.0",
"shakapacker": "9.0.0",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "5.3.1",
"url-loader": "^4.0.0",
Expand Down
23 changes: 19 additions & 4 deletions spec/dummy/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3236,6 +3236,11 @@ find-up@^5.0.0:
locate-path "^6.0.0"
path-exists "^4.0.0"

flat@^5.0.2:
version "5.0.2"
resolved "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241"
integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==

follow-redirects@^1.0.0:
version "1.14.5"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.5.tgz#f09a5848981d3c772b5392309778523f8d85c381"
Expand Down Expand Up @@ -5648,13 +5653,14 @@ sha.js@^2.4.0, sha.js@^2.4.8:
inherits "^2.0.1"
safe-buffer "^5.0.1"

shakapacker@8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/shakapacker/-/shakapacker-8.2.0.tgz#c7bed87b8be2ae565cfe616f68552be545c77e14"
integrity sha512-Ct7BFqJVnKbxdqCzG+ja7Q6LPt/PlB7sSVBfG5jsAvmVCADM05cuoNwEgYNjFGKbDzHAxUqy5XgoI9Y030+JKQ==
shakapacker@9.0.0:
version "9.0.0"
resolved "https://registry.npmjs.org/shakapacker/-/shakapacker-9.0.0.tgz#36fd2e81ffa3a01075222526b2b079bfd60a6efc"
integrity sha512-q+8VU3AQhPpCLlZmEmyooELmpa10FPXk631rrg46pLAYO40jnEeyK01BtI0SVNvz/nI+QFz1DwZE8NKVk/PRgw==
dependencies:
js-yaml "^4.1.0"
path-complete-extname "^1.0.0"
webpack-merge "^5.8.0"

shallow-clone@^3.0.0:
version "3.0.1"
Expand Down Expand Up @@ -6340,6 +6346,15 @@ webpack-merge@5, webpack-merge@^5.7.3:
clone-deep "^4.0.1"
wildcard "^2.0.0"

webpack-merge@^5.8.0:
version "5.10.0"
resolved "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177"
integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==
dependencies:
clone-deep "^4.0.1"
flat "^5.0.2"
wildcard "^2.0.0"

webpack-sources@^3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
Expand Down
Loading