Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
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
100 changes: 100 additions & 0 deletions spec/dummy/bin/shakapacker-precompile-hook
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/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)
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 (cross-platform)
task_list = `bundle exec rails -T 2>&1`
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
4 changes: 4 additions & 0 deletions spec/dummy/config/shakapacker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ default: &default
source_entry_path: packs
public_root_path: public

# 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
36 changes: 32 additions & 4 deletions spec/dummy/config/webpack/commonWebpackConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,38 @@ const sassLoaderConfig = {
},
};

const scssConfigIndex = baseClientWebpackConfig.module.rules.findIndex((config) =>
'.scss'.match(config.test),
);
baseClientWebpackConfig.module.rules[scssConfigIndex]?.use.push(sassLoaderConfig);
// Add sass-resources-loader to all SCSS rules (both .scss and .module.scss)
baseClientWebpackConfig.module.rules.forEach((rule) => {
if (rule.test && '.scss'.match(rule.test) && Array.isArray(rule.use)) {
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`
baseClientWebpackConfig.module.rules.forEach((rule) => {
if (Array.isArray(rule.use)) {
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