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
3 changes: 3 additions & 0 deletions .lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ pre-commit:
rubocop:
run: bin/lefthook/ruby-lint all-changed

eslint:
run: bin/lefthook/eslint-lint all-changed

prettier:
run: bin/lefthook/prettier-format all-changed

Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@ Changes since the last non-beta release.
#### 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).

- **Shakapacker 9.1.0 Upgrade**: Upgraded Shakapacker from 9.0.0 to 9.1.0. This minor version update includes bug fixes and improvements. Updated webpack configuration in Pro dummy apps to use forEach pattern for better compatibility with multiple SCSS rules. [PR 1921](https://github.com/shakacode/react_on_rails/pull/1921) 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
102 changes: 100 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ These requirements are non-negotiable. CI will fail if not followed.

Git hooks will automatically run linting on **all changed files (staged + unstaged + untracked)** before each commit - making it fast while preventing CI failures!

Pre-commit hooks automatically run:
- **RuboCop** (auto-fix Ruby code style)
- **ESLint** (auto-fix JS/TS code style)
- **Prettier** (auto-format all supported files)
- **Trailing newline checks** (ensure all files end with newlines)

**Note:** Git hooks are for React on Rails gem developers only, not for users who install the gem.

## Development Commands
Expand Down Expand Up @@ -90,13 +96,17 @@ Git hooks will automatically run linting on **all changed files (staged + unstag

## Project Architecture

### Dual Package Structure
### Monorepo Structure

This project maintains both a Ruby gem and an NPM package:
This is a monorepo containing both the open-source package and the Pro package:

- **Open Source**: Root directory contains the main React on Rails gem and package
- **Pro Package**: `react_on_rails_pro/` contains the Pro features (separate linting/formatting config)
- **Ruby gem**: Located in `lib/`, provides Rails integration and server-side rendering
- **NPM package**: Located in `packages/react-on-rails/src/`, provides client-side React integration

**IMPORTANT**: The `react_on_rails_pro/` directory has its own Prettier/ESLint configuration. When CI runs, it lints both directories separately. The pre-commit hooks will catch issues in both directories.

### Core Components

#### Ruby Side (`lib/react_on_rails/`)
Expand Down Expand Up @@ -127,6 +137,94 @@ This project maintains both a Ruby gem and an NPM package:
- **Examples**: Generated via rake tasks for different webpack configurations
- **Rake tasks**: Defined in `rakelib/` for various development operations

## Debugging Webpack Configuration Issues

When encountering issues with Webpack/Shakapacker configuration (e.g., components not rendering, CSS modules failing), use this debugging approach:

### 1. Create Debug Scripts

Create temporary debug scripts in the dummy app root to inspect the actual webpack configuration:

```javascript
// debug-webpack-rules.js - Inspect all webpack rules
const { generateWebpackConfig } = require('shakapacker');

const config = generateWebpackConfig();

console.log('=== Webpack Rules ===');
console.log(`Total rules: ${config.module.rules.length}\n`);

config.module.rules.forEach((rule, index) => {
console.log(`\nRule ${index}:`);
console.log(' test:', rule.test);
console.log(' use:', Array.isArray(rule.use) ? rule.use.map(u => typeof u === 'string' ? u : u.loader) : rule.use);

if (rule.test) {
console.log(' Matches .scss:', rule.test.test && rule.test.test('example.scss'));
console.log(' Matches .module.scss:', rule.test.test && rule.test.test('example.module.scss'));
}
});
```

```javascript
// debug-webpack-with-config.js - Inspect config AFTER modifications
const commonWebpackConfig = require('./config/webpack/commonWebpackConfig');

const config = commonWebpackConfig();

console.log('=== Webpack Rules AFTER commonWebpackConfig ===');
config.module.rules.forEach((rule, index) => {
if (rule.test && rule.test.test('example.module.scss')) {
console.log(`\nRule ${index} (CSS Modules):`);
if (Array.isArray(rule.use)) {
rule.use.forEach((loader, i) => {
if (loader.loader && loader.loader.includes('css-loader')) {
console.log(` css-loader options:`, loader.options);
}
});
}
}
});
```

### 2. Run Debug Scripts

```bash
cd spec/dummy # or react_on_rails_pro/spec/dummy
NODE_ENV=test RAILS_ENV=test node debug-webpack-rules.js
NODE_ENV=test RAILS_ENV=test node debug-webpack-with-config.js
```

### 3. Analyze Output

- Verify the rules array structure matches expectations
- Check that loader options are correctly set
- Confirm rules only match intended file patterns
- Ensure modifications don't break existing loaders

### 4. Common Issues & Solutions

**CSS Modules breaking after Shakapacker upgrade:**
- Shakapacker 9.0+ defaults to `namedExport: true` for CSS Modules
- Existing code using `import styles from './file.module.css'` will fail
- Override in webpack config:
```javascript
loader.options.modules.namedExport = false;
loader.options.modules.exportLocalsConvention = 'camelCase';
```

**Rules not matching expected files:**
- Use `.test.test('example.file')` to check regex matching
- Shakapacker may combine multiple file extensions in single rules
- Test with actual filenames from your codebase

### 5. Clean Up

Always remove debug scripts before committing:
```bash
rm debug-*.js
```

## Important Notes

- Use `yalc` for local development when testing with external apps
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", "9.0.0"
gem "shakapacker", "9.1.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 (9.0.0)
shakapacker (9.1.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 (= 9.0.0)
shakapacker (= 9.1.0)
spring (~> 4.0)
sprockets (~> 4.0)
sqlite3 (~> 1.6)
Expand Down
64 changes: 64 additions & 0 deletions bin/lefthook/eslint-lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# Lint and auto-fix JS/TS files with ESLint
set -euo pipefail

CONTEXT="${1:-staged}"
files="$(bin/lefthook/get-changed-files "$CONTEXT" '\.(js|jsx|ts|tsx)$')"

if [ -z "$files" ]; then
echo "✅ No JS/TS files to lint with ESLint"
exit 0
fi

# Separate files into root and Pro directories
root_files=$(echo "$files" | grep -v '^react_on_rails_pro/' || true)
pro_files=$(echo "$files" | grep '^react_on_rails_pro/' || true)

exit_code=0

# Lint root files
if [ -n "$root_files" ]; then
if [ "$CONTEXT" = "all-changed" ]; then
echo "🔍 ESLint on root changed files:"
else
echo "🔍 ESLint on root $CONTEXT files:"
fi
printf " %s\n" $root_files

if ! yarn run eslint $root_files --report-unused-disable-directives --fix; then
exit_code=1
fi

# Re-stage files if running on staged or all-changed context
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
echo $root_files | xargs -r git add
fi
fi

# Lint Pro files (using Pro's ESLint config)
if [ -n "$pro_files" ]; then
if [ "$CONTEXT" = "all-changed" ]; then
echo "🔍 ESLint on Pro changed files:"
else
echo "🔍 ESLint on Pro $CONTEXT files:"
fi
printf " %s\n" $pro_files

# Strip react_on_rails_pro/ prefix for running in Pro directory
pro_files_relative=$(echo "$pro_files" | sed 's|^react_on_rails_pro/||')

if ! (cd react_on_rails_pro && yarn run eslint $pro_files_relative --report-unused-disable-directives --fix); then
exit_code=1
fi

# Re-stage files if running on staged or all-changed context
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
echo $pro_files | xargs -r git add
fi
fi

if [ $exit_code -eq 0 ]; then
echo "✅ ESLint checks passed"
fi

exit $exit_code
48 changes: 38 additions & 10 deletions bin/lefthook/prettier-format
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,45 @@ if [ -z "$files" ]; then
exit 0
fi

if [ "$CONTEXT" = "all-changed" ]; then
echo "💅 Prettier on all changed files:"
else
echo "💅 Prettier on $CONTEXT files:"
# Separate files into root and Pro directories
root_files=$(echo "$files" | grep -v '^react_on_rails_pro/' || true)
pro_files=$(echo "$files" | grep '^react_on_rails_pro/' || true)

# Format root files
if [ -n "$root_files" ]; then
if [ "$CONTEXT" = "all-changed" ]; then
echo "💅 Prettier on root changed files:"
else
echo "💅 Prettier on root $CONTEXT files:"
fi
printf " %s\n" $root_files

yarn run prettier --write $root_files

# Re-stage files if running on staged or all-changed context
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
echo $root_files | xargs -r git add
fi
fi
printf " %s\n" $files

yarn run prettier --write $files
# Format Pro files (using Pro's Prettier config)
if [ -n "$pro_files" ]; then
if [ "$CONTEXT" = "all-changed" ]; then
echo "💅 Prettier on Pro changed files:"
else
echo "💅 Prettier on Pro $CONTEXT files:"
fi
printf " %s\n" $pro_files

# Re-stage files if running on staged or all-changed context
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
echo $files | xargs -r git add
echo "✅ Re-staged formatted files"
# Strip react_on_rails_pro/ prefix for running in Pro directory
pro_files_relative=$(echo "$pro_files" | sed 's|^react_on_rails_pro/||')

(cd react_on_rails_pro && yarn run prettier --write $pro_files_relative)

# Re-stage files if running on staged or all-changed context
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
echo $pro_files | xargs -r git add
fi
fi

echo "✅ Prettier formatting complete"
2 changes: 1 addition & 1 deletion react_on_rails_pro/Gemfile.development_dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ ruby '3.3.7'

gem "react_on_rails", path: "../"

gem "shakapacker", "8.0.0"
gem "shakapacker", "9.1.0"
gem "bootsnap", require: false
gem "rails", "~> 7.1"
gem "puma", "~> 6"
Expand Down
1 change: 1 addition & 0 deletions react_on_rails_pro/spec/dummy/config/shakapacker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ default: &default
public_root_path: public
public_output_path: packs
nested_entries: true
javascript_transpiler: babel

cache_path: tmp/cache/webpacker
check_yarn_integrity: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,17 @@ const sassLoaderConfig = {
};

const baseClientWebpackConfig = generateWebpackConfig();
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 (
Array.isArray(rule.use) &&
rule.test &&
(rule.test.test('example.scss') || rule.test.test('example.module.scss'))
) {
rule.use.push(sassLoaderConfig);
}
});

if (isHMR) {
baseClientWebpackConfig.plugins.push(
Expand All @@ -41,6 +48,34 @@ if (isHMR) {
);
}

const commonWebpackConfig = () => merge({}, baseClientWebpackConfig, commonOptions, aliasConfig);
const commonWebpackConfig = () => {
const config = merge({}, baseClientWebpackConfig, commonOptions, aliasConfig);

// Fix CSS modules for Shakapacker 9.x compatibility
// Shakapacker 9 defaults to namedExport: true, but our code uses default imports
// Override to use the old behavior for backward compatibility
config.module.rules.forEach((rule) => {
if (rule.test && (rule.test.test('example.module.scss') || rule.test.test('example.module.css'))) {
if (Array.isArray(rule.use)) {
rule.use.forEach((loader) => {
if (
loader.loader &&
loader.loader.includes('css-loader') &&
loader.options &&
loader.options.modules
) {
// Disable named exports to support default imports
// eslint-disable-next-line no-param-reassign
loader.options.modules.namedExport = false;
// eslint-disable-next-line no-param-reassign
loader.options.modules.exportLocalsConvention = 'camelCase';
}
});
}
}
});

return config;
};

module.exports = commonWebpackConfig;
2 changes: 1 addition & 1 deletion react_on_rails_pro/spec/dummy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"sass": "^1.43.4",
"sass-loader": "^12.3.0",
"sass-resources-loader": "^2.0.0",
"shakapacker": "8.0.0",
"shakapacker": "9.1.0",
"style-loader": "^3.3.1",
"tailwindcss": "^3.2.7",
"terser-webpack-plugin": "5",
Expand Down
Loading
Loading