Skip to content

Commit 7d61170

Browse files
authored
Upgrade Shakapacker from 9.0.0 to 9.1.0 (#1921)
Upgrade Shakapacker from 9.0.0 to 9.1.0 (#1921) Why Shakapacker 9.0.0 had compatibility issues with CSS Modules. Summary Updated Shakapacker to 9.1.0 across all dummy apps and fixed CSS Modules configuration for backward compatibility. Key improvements - Fixed CSS Modules to support default imports (not named exports) - Enhanced pre-commit hooks to lint Pro directory separately - Added webpack config debugging documentation Impact Existing: CSS Modules continue working with default import syntax New: Shakapacker 9.1.0 with improved stability Risks Breaking: CSS Modules namedExport override required for compatibility
1 parent 74914ab commit 7d61170

File tree

18 files changed

+276
-41
lines changed

18 files changed

+276
-41
lines changed

.lefthook.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ pre-commit:
1111
rubocop:
1212
run: bin/lefthook/ruby-lint all-changed
1313

14+
eslint:
15+
run: bin/lefthook/eslint-lint all-changed
16+
1417
prettier:
1518
run: bin/lefthook/prettier-format all-changed
1619

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@ Changes since the last non-beta release.
3434
#### Changed
3535

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

44+
- **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).
45+
4346
#### Bug Fixes
4447

4548
- **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).

CLAUDE.md

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ These requirements are non-negotiable. CI will fail if not followed.
1717

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

20+
Pre-commit hooks automatically run:
21+
- **RuboCop** (auto-fix Ruby code style)
22+
- **ESLint** (auto-fix JS/TS code style)
23+
- **Prettier** (auto-format all supported files)
24+
- **Trailing newline checks** (ensure all files end with newlines)
25+
2026
**Note:** Git hooks are for React on Rails gem developers only, not for users who install the gem.
2127

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

9197
## Project Architecture
9298

93-
### Dual Package Structure
99+
### Monorepo Structure
94100

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

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

108+
**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.
109+
100110
### Core Components
101111

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

140+
## Debugging Webpack Configuration Issues
141+
142+
When encountering issues with Webpack/Shakapacker configuration (e.g., components not rendering, CSS modules failing), use this debugging approach:
143+
144+
### 1. Create Debug Scripts
145+
146+
Create temporary debug scripts in the dummy app root to inspect the actual webpack configuration:
147+
148+
```javascript
149+
// debug-webpack-rules.js - Inspect all webpack rules
150+
const { generateWebpackConfig } = require('shakapacker');
151+
152+
const config = generateWebpackConfig();
153+
154+
console.log('=== Webpack Rules ===');
155+
console.log(`Total rules: ${config.module.rules.length}\n`);
156+
157+
config.module.rules.forEach((rule, index) => {
158+
console.log(`\nRule ${index}:`);
159+
console.log(' test:', rule.test);
160+
console.log(' use:', Array.isArray(rule.use) ? rule.use.map(u => typeof u === 'string' ? u : u.loader) : rule.use);
161+
162+
if (rule.test) {
163+
console.log(' Matches .scss:', rule.test.test && rule.test.test('example.scss'));
164+
console.log(' Matches .module.scss:', rule.test.test && rule.test.test('example.module.scss'));
165+
}
166+
});
167+
```
168+
169+
```javascript
170+
// debug-webpack-with-config.js - Inspect config AFTER modifications
171+
const commonWebpackConfig = require('./config/webpack/commonWebpackConfig');
172+
173+
const config = commonWebpackConfig();
174+
175+
console.log('=== Webpack Rules AFTER commonWebpackConfig ===');
176+
config.module.rules.forEach((rule, index) => {
177+
if (rule.test && rule.test.test('example.module.scss')) {
178+
console.log(`\nRule ${index} (CSS Modules):`);
179+
if (Array.isArray(rule.use)) {
180+
rule.use.forEach((loader, i) => {
181+
if (loader.loader && loader.loader.includes('css-loader')) {
182+
console.log(` css-loader options:`, loader.options);
183+
}
184+
});
185+
}
186+
}
187+
});
188+
```
189+
190+
### 2. Run Debug Scripts
191+
192+
```bash
193+
cd spec/dummy # or react_on_rails_pro/spec/dummy
194+
NODE_ENV=test RAILS_ENV=test node debug-webpack-rules.js
195+
NODE_ENV=test RAILS_ENV=test node debug-webpack-with-config.js
196+
```
197+
198+
### 3. Analyze Output
199+
200+
- Verify the rules array structure matches expectations
201+
- Check that loader options are correctly set
202+
- Confirm rules only match intended file patterns
203+
- Ensure modifications don't break existing loaders
204+
205+
### 4. Common Issues & Solutions
206+
207+
**CSS Modules breaking after Shakapacker upgrade:**
208+
- Shakapacker 9.0+ defaults to `namedExport: true` for CSS Modules
209+
- Existing code using `import styles from './file.module.css'` will fail
210+
- Override in webpack config:
211+
```javascript
212+
loader.options.modules.namedExport = false;
213+
loader.options.modules.exportLocalsConvention = 'camelCase';
214+
```
215+
216+
**Rules not matching expected files:**
217+
- Use `.test.test('example.file')` to check regex matching
218+
- Shakapacker may combine multiple file extensions in single rules
219+
- Test with actual filenames from your codebase
220+
221+
### 5. Clean Up
222+
223+
Always remove debug scripts before committing:
224+
```bash
225+
rm debug-*.js
226+
```
227+
130228
## Important Notes
131229

132230
- Use `yalc` for local development when testing with external apps

Gemfile.development_dependencies

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# frozen_string_literal: true
22

3-
gem "shakapacker", "9.0.0"
3+
gem "shakapacker", "9.1.0"
44
gem "bootsnap", require: false
55
gem "rails", "~> 7.1"
66

Gemfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ GEM
342342
rubyzip (>= 1.2.2, < 3.0)
343343
websocket (~> 1.0)
344344
semantic_range (3.1.0)
345-
shakapacker (9.0.0)
345+
shakapacker (9.1.0)
346346
activesupport (>= 5.2)
347347
package_json
348348
rack-proxy (>= 0.6.1)
@@ -440,7 +440,7 @@ DEPENDENCIES
440440
scss_lint
441441
sdoc
442442
selenium-webdriver (= 4.9.0)
443-
shakapacker (= 9.0.0)
443+
shakapacker (= 9.1.0)
444444
spring (~> 4.0)
445445
sprockets (~> 4.0)
446446
sqlite3 (~> 1.6)

bin/lefthook/eslint-lint

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#!/usr/bin/env bash
2+
# Lint and auto-fix JS/TS files with ESLint
3+
set -euo pipefail
4+
5+
CONTEXT="${1:-staged}"
6+
files="$(bin/lefthook/get-changed-files "$CONTEXT" '\.(js|jsx|ts|tsx)$')"
7+
8+
if [ -z "$files" ]; then
9+
echo "✅ No JS/TS files to lint with ESLint"
10+
exit 0
11+
fi
12+
13+
# Separate files into root and Pro directories
14+
root_files=$(echo "$files" | grep -v '^react_on_rails_pro/' || true)
15+
pro_files=$(echo "$files" | grep '^react_on_rails_pro/' || true)
16+
17+
exit_code=0
18+
19+
# Lint root files
20+
if [ -n "$root_files" ]; then
21+
if [ "$CONTEXT" = "all-changed" ]; then
22+
echo "🔍 ESLint on root changed files:"
23+
else
24+
echo "🔍 ESLint on root $CONTEXT files:"
25+
fi
26+
printf " %s\n" $root_files
27+
28+
if ! yarn run eslint $root_files --report-unused-disable-directives --fix; then
29+
exit_code=1
30+
fi
31+
32+
# Re-stage files if running on staged or all-changed context
33+
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
34+
echo $root_files | xargs -r git add
35+
fi
36+
fi
37+
38+
# Lint Pro files (using Pro's ESLint config)
39+
if [ -n "$pro_files" ]; then
40+
if [ "$CONTEXT" = "all-changed" ]; then
41+
echo "🔍 ESLint on Pro changed files:"
42+
else
43+
echo "🔍 ESLint on Pro $CONTEXT files:"
44+
fi
45+
printf " %s\n" $pro_files
46+
47+
# Strip react_on_rails_pro/ prefix for running in Pro directory
48+
pro_files_relative=$(echo "$pro_files" | sed 's|^react_on_rails_pro/||')
49+
50+
if ! (cd react_on_rails_pro && yarn run eslint $pro_files_relative --report-unused-disable-directives --fix); then
51+
exit_code=1
52+
fi
53+
54+
# Re-stage files if running on staged or all-changed context
55+
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
56+
echo $pro_files | xargs -r git add
57+
fi
58+
fi
59+
60+
if [ $exit_code -eq 0 ]; then
61+
echo "✅ ESLint checks passed"
62+
fi
63+
64+
exit $exit_code

bin/lefthook/prettier-format

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,45 @@ if [ -z "$files" ]; then
1010
exit 0
1111
fi
1212

13-
if [ "$CONTEXT" = "all-changed" ]; then
14-
echo "💅 Prettier on all changed files:"
15-
else
16-
echo "💅 Prettier on $CONTEXT files:"
13+
# Separate files into root and Pro directories
14+
root_files=$(echo "$files" | grep -v '^react_on_rails_pro/' || true)
15+
pro_files=$(echo "$files" | grep '^react_on_rails_pro/' || true)
16+
17+
# Format root files
18+
if [ -n "$root_files" ]; then
19+
if [ "$CONTEXT" = "all-changed" ]; then
20+
echo "💅 Prettier on root changed files:"
21+
else
22+
echo "💅 Prettier on root $CONTEXT files:"
23+
fi
24+
printf " %s\n" $root_files
25+
26+
yarn run prettier --write $root_files
27+
28+
# Re-stage files if running on staged or all-changed context
29+
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
30+
echo $root_files | xargs -r git add
31+
fi
1732
fi
18-
printf " %s\n" $files
1933

20-
yarn run prettier --write $files
34+
# Format Pro files (using Pro's Prettier config)
35+
if [ -n "$pro_files" ]; then
36+
if [ "$CONTEXT" = "all-changed" ]; then
37+
echo "💅 Prettier on Pro changed files:"
38+
else
39+
echo "💅 Prettier on Pro $CONTEXT files:"
40+
fi
41+
printf " %s\n" $pro_files
2142

22-
# Re-stage files if running on staged or all-changed context
23-
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
24-
echo $files | xargs -r git add
25-
echo "✅ Re-staged formatted files"
43+
# Strip react_on_rails_pro/ prefix for running in Pro directory
44+
pro_files_relative=$(echo "$pro_files" | sed 's|^react_on_rails_pro/||')
45+
46+
(cd react_on_rails_pro && yarn run prettier --write $pro_files_relative)
47+
48+
# Re-stage files if running on staged or all-changed context
49+
if [ "$CONTEXT" = "staged" ] || [ "$CONTEXT" = "all-changed" ]; then
50+
echo $pro_files | xargs -r git add
51+
fi
2652
fi
53+
54+
echo "✅ Prettier formatting complete"

react_on_rails_pro/Gemfile.development_dependencies

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ ruby '3.3.7'
77

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

10-
gem "shakapacker", "8.0.0"
10+
gem "shakapacker", "9.1.0"
1111
gem "bootsnap", require: false
1212
gem "rails", "~> 7.1"
1313
gem "puma", "~> 6"

react_on_rails_pro/spec/dummy/config/shakapacker.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ default: &default
66
public_root_path: public
77
public_output_path: packs
88
nested_entries: true
9+
javascript_transpiler: babel
910

1011
cache_path: tmp/cache/webpacker
1112
check_yarn_integrity: false

react_on_rails_pro/spec/dummy/config/webpack/commonWebpackConfig.js

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,17 @@ const sassLoaderConfig = {
2626
};
2727

2828
const baseClientWebpackConfig = generateWebpackConfig();
29-
const scssConfigIndex = baseClientWebpackConfig.module.rules.findIndex((config) =>
30-
'.scss'.match(config.test),
31-
);
32-
baseClientWebpackConfig.module.rules[scssConfigIndex].use.push(sassLoaderConfig);
29+
30+
// Add sass-resources-loader to all SCSS rules (both .scss and .module.scss)
31+
baseClientWebpackConfig.module.rules.forEach((rule) => {
32+
if (
33+
Array.isArray(rule.use) &&
34+
rule.test &&
35+
(rule.test.test('example.scss') || rule.test.test('example.module.scss'))
36+
) {
37+
rule.use.push(sassLoaderConfig);
38+
}
39+
});
3340

3441
if (isHMR) {
3542
baseClientWebpackConfig.plugins.push(
@@ -41,6 +48,34 @@ if (isHMR) {
4148
);
4249
}
4350

44-
const commonWebpackConfig = () => merge({}, baseClientWebpackConfig, commonOptions, aliasConfig);
51+
const commonWebpackConfig = () => {
52+
const config = merge({}, baseClientWebpackConfig, commonOptions, aliasConfig);
53+
54+
// Fix CSS modules for Shakapacker 9.x compatibility
55+
// Shakapacker 9 defaults to namedExport: true, but our code uses default imports
56+
// Override to use the old behavior for backward compatibility
57+
config.module.rules.forEach((rule) => {
58+
if (rule.test && (rule.test.test('example.module.scss') || rule.test.test('example.module.css'))) {
59+
if (Array.isArray(rule.use)) {
60+
rule.use.forEach((loader) => {
61+
if (
62+
loader.loader &&
63+
loader.loader.includes('css-loader') &&
64+
loader.options &&
65+
loader.options.modules
66+
) {
67+
// Disable named exports to support default imports
68+
// eslint-disable-next-line no-param-reassign
69+
loader.options.modules.namedExport = false;
70+
// eslint-disable-next-line no-param-reassign
71+
loader.options.modules.exportLocalsConvention = 'camelCase';
72+
}
73+
});
74+
}
75+
}
76+
});
77+
78+
return config;
79+
};
4580

4681
module.exports = commonWebpackConfig;

0 commit comments

Comments
 (0)